From f110eaec4437667ac6c0f60a720e83d052d30aa9 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 16 Jul 2024 15:35:07 +0100
Subject: [PATCH 01/37] Create tab-item component
---
src/app/tab/tab-item/tab-item.component.css | 0
src/app/tab/tab-item/tab-item.component.html | 3 +++
.../tab/tab-item/tab-item.component.spec.ts | 23 +++++++++++++++++++
src/app/tab/tab-item/tab-item.component.ts | 13 +++++++++++
4 files changed, 39 insertions(+)
create mode 100644 src/app/tab/tab-item/tab-item.component.css
create mode 100644 src/app/tab/tab-item/tab-item.component.html
create mode 100644 src/app/tab/tab-item/tab-item.component.spec.ts
create mode 100644 src/app/tab/tab-item/tab-item.component.ts
diff --git a/src/app/tab/tab-item/tab-item.component.css b/src/app/tab/tab-item/tab-item.component.css
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/tab/tab-item/tab-item.component.html b/src/app/tab/tab-item/tab-item.component.html
new file mode 100644
index 00000000..751f66c4
--- /dev/null
+++ b/src/app/tab/tab-item/tab-item.component.html
@@ -0,0 +1,3 @@
+@if (active()) {
+
+}
diff --git a/src/app/tab/tab-item/tab-item.component.spec.ts b/src/app/tab/tab-item/tab-item.component.spec.ts
new file mode 100644
index 00000000..204f325f
--- /dev/null
+++ b/src/app/tab/tab-item/tab-item.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TabItemComponent } from './tab-item.component';
+
+describe('TabItemComponent', () => {
+ let component: TabItemComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TabItemComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TabItemComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/tab/tab-item/tab-item.component.ts b/src/app/tab/tab-item/tab-item.component.ts
new file mode 100644
index 00000000..e7cb8b69
--- /dev/null
+++ b/src/app/tab/tab-item/tab-item.component.ts
@@ -0,0 +1,13 @@
+import { Component, input, model } from '@angular/core';
+
+@Component({
+ selector: 'tab-item',
+ standalone: true,
+ imports: [],
+ templateUrl: './tab-item.component.html',
+ styleUrl: './tab-item.component.css',
+})
+export class TabItemComponent {
+ public active = model(false);
+ public title = input.required();
+}
From d827bba98fd5c9624b1deaef173b413513b163e1 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 16 Jul 2024 15:35:24 +0100
Subject: [PATCH 02/37] Create tabs component
---
src/app/tab/tabs/tabs.component.css | 0
src/app/tab/tabs/tabs.component.html | 8 ++++++++
src/app/tab/tabs/tabs.component.spec.ts | 23 +++++++++++++++++++++++
src/app/tab/tabs/tabs.component.ts | 18 ++++++++++++++++++
4 files changed, 49 insertions(+)
create mode 100644 src/app/tab/tabs/tabs.component.css
create mode 100644 src/app/tab/tabs/tabs.component.html
create mode 100644 src/app/tab/tabs/tabs.component.spec.ts
create mode 100644 src/app/tab/tabs/tabs.component.ts
diff --git a/src/app/tab/tabs/tabs.component.css b/src/app/tab/tabs/tabs.component.css
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/tab/tabs/tabs.component.html b/src/app/tab/tabs/tabs.component.html
new file mode 100644
index 00000000..1f25a50a
--- /dev/null
+++ b/src/app/tab/tabs/tabs.component.html
@@ -0,0 +1,8 @@
+
+ @for (tab of tabs; track tab.title) {
+ -
+ {{ tab.title() }}
+
+ }
+
+
diff --git a/src/app/tab/tabs/tabs.component.spec.ts b/src/app/tab/tabs/tabs.component.spec.ts
new file mode 100644
index 00000000..c2822189
--- /dev/null
+++ b/src/app/tab/tabs/tabs.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TabsComponent } from './tabs.component';
+
+describe('TabsComponent', () => {
+ let component: TabsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TabsComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TabsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/tab/tabs/tabs.component.ts b/src/app/tab/tabs/tabs.component.ts
new file mode 100644
index 00000000..a7115583
--- /dev/null
+++ b/src/app/tab/tabs/tabs.component.ts
@@ -0,0 +1,18 @@
+import { Component, ContentChildren, QueryList } from '@angular/core';
+import { TabItemComponent } from '../tab-item/tab-item.component';
+
+@Component({
+ selector: 'tabs',
+ standalone: true,
+ imports: [TabItemComponent],
+ templateUrl: './tabs.component.html',
+ styleUrl: './tabs.component.css',
+})
+export class TabsComponent {
+ @ContentChildren(TabItemComponent) tabs!: QueryList;
+
+ selectTab(selectedTab: TabItemComponent) {
+ this.tabs.filter(tab => tab.active()).forEach(tab => tab.active.set(false));
+ selectedTab.active.set(true);
+ }
+}
From c247e9c881807ac1cbf4cde398494e468ed86c3e Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 16 Jul 2024 15:37:23 +0100
Subject: [PATCH 03/37] Create treemap component and refactor chart logic from
estimator into it
---
.../carbon-estimation-treemap.component.css | 0
.../carbon-estimation-treemap.component.html | 12 ++
...arbon-estimation-treemap.component.spec.ts | 23 +++
.../carbon-estimation-treemap.component.ts | 182 ++++++++++++++++++
4 files changed, 217 insertions(+)
create mode 100644 src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.css
create mode 100644 src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.html
create mode 100644 src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
create mode 100644 src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.css b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.css
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.html b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.html
new file mode 100644
index 00000000..245f5c95
--- /dev/null
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.html
@@ -0,0 +1,12 @@
+
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
new file mode 100644
index 00000000..5e930058
--- /dev/null
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CarbonEstimationTreemapComponent } from './carbon-estimation-treemap.component';
+
+describe('CarbonEstimationTreemapComponent', () => {
+ let component: CarbonEstimationTreemapComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [CarbonEstimationTreemapComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(CarbonEstimationTreemapComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
new file mode 100644
index 00000000..d4cbfd86
--- /dev/null
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
@@ -0,0 +1,182 @@
+import { Component, OnInit, ViewChild, effect, input } from '@angular/core';
+import { ApexAxisChartSeries, ChartComponent, NgApexchartsModule } from 'ng-apexcharts';
+import { CarbonEstimation, ChartOptions } from '../types/carbon-estimator';
+import {
+ chartOptions,
+ tooltipFormatter,
+ EmissionsColours,
+ EmissionsLabels,
+ SVG,
+} from '../carbon-estimation/carbon-estimation.constants';
+import { NumberObject, sumValues } from '../utils/number-object';
+import { startCase } from 'lodash-es';
+
+type ApexChartDataItem = { x: string; y: number; meta: { svg: string; parent: string } };
+
+type ApexChartSeries = {
+ name: string;
+ color: string;
+ data: ApexChartDataItem[];
+};
+
+@Component({
+ selector: 'carbon-estimation-treemap',
+ standalone: true,
+ imports: [NgApexchartsModule],
+ templateUrl: './carbon-estimation-treemap.component.html',
+ styleUrl: './carbon-estimation-treemap.component.css',
+})
+export class CarbonEstimationTreemapComponent implements OnInit {
+ public carbonEstimation = input.required();
+ public chartHeight = input.required();
+
+ public emissions: ApexAxisChartSeries = [];
+ public emissionAriaLabel = 'Estimations of emissions.';
+
+ public chartOptions: ChartOptions = chartOptions;
+ private tooltipFormatter = tooltipFormatter;
+
+ @ViewChild('chart') chart: ChartComponent | undefined;
+
+ constructor() {
+ effect(() => {
+ this.emissions = this.getOverallEmissionPercentages(this.carbonEstimation());
+ this.emissionAriaLabel = this.getAriaLabel(this.emissions);
+ const chartHeight = this.chartHeight();
+ if (chartHeight !== this.chartOptions.chart.height) {
+ this.chart?.updateOptions({ chart: { height: chartHeight } });
+ }
+ });
+ }
+
+ public ngOnInit(): void {
+ const chartHeight = this.chartHeight();
+ if (chartHeight > 0) {
+ this.chartOptions.chart.height = chartHeight;
+ }
+ }
+
+ private getOverallEmissionPercentages(carbonEstimation: CarbonEstimation): ApexAxisChartSeries {
+ return [
+ {
+ name: `${EmissionsLabels.Upstream} - ${this.getOverallPercentageLabel(carbonEstimation.upstreamEmissions)}`,
+ color: EmissionsColours.Upstream,
+ data: this.getEmissionPercentages(carbonEstimation.upstreamEmissions, EmissionsLabels.Upstream),
+ },
+ {
+ name: `${EmissionsLabels.Direct} - ${this.getOverallPercentageLabel(carbonEstimation.directEmissions)}`,
+ color: EmissionsColours.Direct,
+ data: this.getEmissionPercentages(carbonEstimation.directEmissions, EmissionsLabels.Direct),
+ },
+ {
+ name: `${EmissionsLabels.Indirect} - ${this.getOverallPercentageLabel(carbonEstimation.indirectEmissions)}`,
+ color: EmissionsColours.Indirect,
+ data: this.getEmissionPercentages(carbonEstimation.indirectEmissions, EmissionsLabels.Indirect),
+ },
+ {
+ name: `${EmissionsLabels.Downstream} - ${this.getOverallPercentageLabel(carbonEstimation.downstreamEmissions)}`,
+ color: EmissionsColours.Downstream,
+ data: this.getEmissionPercentages(carbonEstimation.downstreamEmissions, EmissionsLabels.Downstream),
+ },
+ ].filter(entry => entry.data.length !== 0);
+ }
+
+ private getAriaLabel(emission: ApexAxisChartSeries): string {
+ return `Estimation of emissions. ${emission.map(entry => this.getAriaLabelForCategory(entry as ApexChartSeries)).join(' ')}`;
+ }
+
+ private getAriaLabelForCategory(series: ApexChartSeries): string {
+ const category = series.name.replace('-', 'are');
+ return `${category}${this.getEmissionMadeUp(series.data)}`;
+ }
+
+ private getEmissionMadeUp(emission: ApexChartDataItem[]): string {
+ if (emission.length === 0) {
+ return '.';
+ }
+ return `, made up of ${emission.map(item => `${item.x} ${this.tooltipFormatter(item.y)}`).join(', ')}.`;
+ }
+
+ private getOverallPercentageLabel = (emissions: NumberObject): string => {
+ const percentage = sumValues(emissions);
+ return percentage < 1 ? '<1%' : Math.round(percentage) + '%';
+ };
+
+ private getEmissionPercentages(emissions: NumberObject, parent: string): ApexChartDataItem[] {
+ return (
+ Object.entries(emissions)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ .filter(([_key, value]) => value !== 0)
+ .map(([_key, value]) => this.getDataItem(_key, value, parent))
+ );
+ }
+
+ private getDataItem(key: string, value: number, parent: string): ApexChartDataItem {
+ switch (key) {
+ case 'software':
+ return this.getDataItemObject('Software - Off the Shelf', value, SVG.WEB, parent);
+ case 'saas':
+ return this.getDataItemObject('SaaS', value, SVG.WEB, parent);
+ case 'employee':
+ return this.getDataItemObject(this.getEmployeeLabel(parent), value, SVG.DEVICES, parent);
+ case 'endUser':
+ return this.getDataItemObject('End-User Devices', value, SVG.DEVICES, parent);
+ case 'network':
+ return this.getDataItemObject(this.getNetworkLabel(parent), value, SVG.ROUTER, parent);
+ case 'server':
+ return this.getDataItemObject(this.getServerLabel(parent), value, SVG.STORAGE, parent);
+ case 'managed':
+ return this.getDataItemObject('Managed Services', value, SVG.STORAGE, parent);
+ case 'cloud':
+ return this.getDataItemObject('Cloud Services', value, SVG.CLOUD, parent);
+ case 'networkTransfer':
+ return this.getDataItemObject('Network Data Transfer', value, SVG.CELL_TOWER, parent);
+ default:
+ return this.getDataItemObject(startCase(key), value, '', parent);
+ }
+ }
+
+ private getDataItemObject(x: string, y: number, svg: string, parent: string): ApexChartDataItem {
+ return {
+ x,
+ y,
+ meta: {
+ svg,
+ parent,
+ },
+ };
+ }
+
+ private getEmployeeLabel(key: string): string {
+ switch (key) {
+ case 'Upstream Emissions':
+ return 'Employee Hardware';
+ case 'Direct Emissions':
+ return 'Employee Devices';
+ default:
+ return startCase(key);
+ }
+ }
+
+ private getNetworkLabel(key: string): string {
+ switch (key) {
+ case 'Upstream Emissions':
+ return 'Networking and Infrastructure Hardware';
+ case 'Direct Emissions':
+ return 'Networking and Infrastructure';
+ default:
+ return startCase(key);
+ }
+ }
+
+ private getServerLabel(key: string): string {
+ switch (key) {
+ case 'Upstream Emissions':
+ return 'Servers and Storage Hardware';
+ case 'Direct Emissions':
+ return 'Servers and Storage';
+ default:
+ return startCase(key);
+ }
+ }
+}
From effaf054c20249b1a997bb3ec47cb3c240f95cb7 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Wed, 17 Jul 2024 15:24:26 +0100
Subject: [PATCH 04/37] Create estimations util service and move functions from
treemap
---
.../carbon-estimation-treemap.component.ts | 77 +++----------------
.../carbon-estimation-util.service.spec.ts | 55 +++++++++++++
.../carbon-estimation-util.service.ts | 76 ++++++++++++++++++
3 files changed, 140 insertions(+), 68 deletions(-)
create mode 100644 src/app/services/carbon-estimation-util.service.spec.ts
create mode 100644 src/app/services/carbon-estimation-util.service.ts
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
index d4cbfd86..e58bda3d 100644
--- a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
@@ -6,10 +6,9 @@ import {
tooltipFormatter,
EmissionsColours,
EmissionsLabels,
- SVG,
} from '../carbon-estimation/carbon-estimation.constants';
-import { NumberObject, sumValues } from '../utils/number-object';
-import { startCase } from 'lodash-es';
+import { NumberObject } from '../utils/number-object';
+import { CarbonEstimationUtilService } from '../services/carbon-estimation-util.service';
type ApexChartDataItem = { x: string; y: number; meta: { svg: string; parent: string } };
@@ -38,7 +37,7 @@ export class CarbonEstimationTreemapComponent implements OnInit {
@ViewChild('chart') chart: ChartComponent | undefined;
- constructor() {
+ constructor(private carbonEstimationUtilService: CarbonEstimationUtilService) {
effect(() => {
this.emissions = this.getOverallEmissionPercentages(this.carbonEstimation());
this.emissionAriaLabel = this.getAriaLabel(this.emissions);
@@ -59,22 +58,22 @@ export class CarbonEstimationTreemapComponent implements OnInit {
private getOverallEmissionPercentages(carbonEstimation: CarbonEstimation): ApexAxisChartSeries {
return [
{
- name: `${EmissionsLabels.Upstream} - ${this.getOverallPercentageLabel(carbonEstimation.upstreamEmissions)}`,
+ name: `${EmissionsLabels.Upstream} - ${this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.upstreamEmissions)}`,
color: EmissionsColours.Upstream,
data: this.getEmissionPercentages(carbonEstimation.upstreamEmissions, EmissionsLabels.Upstream),
},
{
- name: `${EmissionsLabels.Direct} - ${this.getOverallPercentageLabel(carbonEstimation.directEmissions)}`,
+ name: `${EmissionsLabels.Direct} - ${this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.directEmissions)}`,
color: EmissionsColours.Direct,
data: this.getEmissionPercentages(carbonEstimation.directEmissions, EmissionsLabels.Direct),
},
{
- name: `${EmissionsLabels.Indirect} - ${this.getOverallPercentageLabel(carbonEstimation.indirectEmissions)}`,
+ name: `${EmissionsLabels.Indirect} - ${this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.indirectEmissions)}`,
color: EmissionsColours.Indirect,
data: this.getEmissionPercentages(carbonEstimation.indirectEmissions, EmissionsLabels.Indirect),
},
{
- name: `${EmissionsLabels.Downstream} - ${this.getOverallPercentageLabel(carbonEstimation.downstreamEmissions)}`,
+ name: `${EmissionsLabels.Downstream} - ${this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.downstreamEmissions)}`,
color: EmissionsColours.Downstream,
data: this.getEmissionPercentages(carbonEstimation.downstreamEmissions, EmissionsLabels.Downstream),
},
@@ -97,11 +96,6 @@ export class CarbonEstimationTreemapComponent implements OnInit {
return `, made up of ${emission.map(item => `${item.x} ${this.tooltipFormatter(item.y)}`).join(', ')}.`;
}
- private getOverallPercentageLabel = (emissions: NumberObject): string => {
- const percentage = sumValues(emissions);
- return percentage < 1 ? '<1%' : Math.round(percentage) + '%';
- };
-
private getEmissionPercentages(emissions: NumberObject, parent: string): ApexChartDataItem[] {
return (
Object.entries(emissions)
@@ -112,28 +106,8 @@ export class CarbonEstimationTreemapComponent implements OnInit {
}
private getDataItem(key: string, value: number, parent: string): ApexChartDataItem {
- switch (key) {
- case 'software':
- return this.getDataItemObject('Software - Off the Shelf', value, SVG.WEB, parent);
- case 'saas':
- return this.getDataItemObject('SaaS', value, SVG.WEB, parent);
- case 'employee':
- return this.getDataItemObject(this.getEmployeeLabel(parent), value, SVG.DEVICES, parent);
- case 'endUser':
- return this.getDataItemObject('End-User Devices', value, SVG.DEVICES, parent);
- case 'network':
- return this.getDataItemObject(this.getNetworkLabel(parent), value, SVG.ROUTER, parent);
- case 'server':
- return this.getDataItemObject(this.getServerLabel(parent), value, SVG.STORAGE, parent);
- case 'managed':
- return this.getDataItemObject('Managed Services', value, SVG.STORAGE, parent);
- case 'cloud':
- return this.getDataItemObject('Cloud Services', value, SVG.CLOUD, parent);
- case 'networkTransfer':
- return this.getDataItemObject('Network Data Transfer', value, SVG.CELL_TOWER, parent);
- default:
- return this.getDataItemObject(startCase(key), value, '', parent);
- }
+ const { label, svg } = this.carbonEstimationUtilService.getLabelAndSvg(key, parent);
+ return this.getDataItemObject(label, value, svg, parent);
}
private getDataItemObject(x: string, y: number, svg: string, parent: string): ApexChartDataItem {
@@ -146,37 +120,4 @@ export class CarbonEstimationTreemapComponent implements OnInit {
},
};
}
-
- private getEmployeeLabel(key: string): string {
- switch (key) {
- case 'Upstream Emissions':
- return 'Employee Hardware';
- case 'Direct Emissions':
- return 'Employee Devices';
- default:
- return startCase(key);
- }
- }
-
- private getNetworkLabel(key: string): string {
- switch (key) {
- case 'Upstream Emissions':
- return 'Networking and Infrastructure Hardware';
- case 'Direct Emissions':
- return 'Networking and Infrastructure';
- default:
- return startCase(key);
- }
- }
-
- private getServerLabel(key: string): string {
- switch (key) {
- case 'Upstream Emissions':
- return 'Servers and Storage Hardware';
- case 'Direct Emissions':
- return 'Servers and Storage';
- default:
- return startCase(key);
- }
- }
}
diff --git a/src/app/services/carbon-estimation-util.service.spec.ts b/src/app/services/carbon-estimation-util.service.spec.ts
new file mode 100644
index 00000000..7b85b60c
--- /dev/null
+++ b/src/app/services/carbon-estimation-util.service.spec.ts
@@ -0,0 +1,55 @@
+import { TestBed } from '@angular/core/testing';
+
+import { CarbonEstimationUtilService } from './carbon-estimation-util.service';
+
+describe('CarbonEstimationUtilService', () => {
+ let service: CarbonEstimationUtilService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({});
+ service = TestBed.inject(CarbonEstimationUtilService);
+ });
+
+ it('should round numbers and add a percentage sign', () => {
+ expect(service.getPercentageLabel(1)).toBe('1%');
+ expect(service.getPercentageLabel(1.5)).toBe('2%');
+ expect(service.getPercentageLabel(1.499)).toBe('1%');
+ });
+
+ it('should return <1% when the percentage is less than 1', () => {
+ expect(service.getPercentageLabel(0.999)).toBe('<1%');
+ });
+
+ it('should sum the values of an object and return a percentage string', () => {
+ const emissions = { a: 10, b: 25, c: 1 };
+ expect(service.getOverallPercentageLabel(emissions)).toBe('36%');
+
+ const emissions2 = { a: 0.1, b: 0.2, c: 0.5 };
+ expect(service.getOverallPercentageLabel(emissions2)).toBe('<1%');
+ });
+
+ it('should return the correct label and svg for a given key', () => {
+ expect(service.getLabelAndSvg('software')).toEqual({ label: 'Software - Off the Shelf', svg: 'web-logo' });
+ expect(service.getLabelAndSvg('saas')).toEqual({ label: 'SaaS', svg: 'web-logo' });
+ expect(service.getLabelAndSvg('employee', 'Upstream Emissions')).toEqual({
+ label: 'Employee Hardware',
+ svg: 'devices-logo',
+ });
+ expect(service.getLabelAndSvg('endUser')).toEqual({ label: 'End-User Devices', svg: 'devices-logo' });
+ expect(service.getLabelAndSvg('network', 'Direct Emissions')).toEqual({
+ label: 'Networking and Infrastructure',
+ svg: 'router-logo',
+ });
+ expect(service.getLabelAndSvg('server', 'Direct Emissions')).toEqual({
+ label: 'Servers and Storage',
+ svg: 'storage-logo',
+ });
+ expect(service.getLabelAndSvg('managed')).toEqual({ label: 'Managed Services', svg: 'storage-logo' });
+ expect(service.getLabelAndSvg('cloud')).toEqual({ label: 'Cloud Services', svg: 'cloud-logo' });
+ expect(service.getLabelAndSvg('networkTransfer')).toEqual({
+ label: 'Network Data Transfer',
+ svg: 'cell-tower-logo',
+ });
+ expect(service.getLabelAndSvg('unknown')).toEqual({ label: 'Unknown', svg: '' });
+ });
+});
diff --git a/src/app/services/carbon-estimation-util.service.ts b/src/app/services/carbon-estimation-util.service.ts
new file mode 100644
index 00000000..cfb2b06b
--- /dev/null
+++ b/src/app/services/carbon-estimation-util.service.ts
@@ -0,0 +1,76 @@
+import { Injectable } from '@angular/core';
+import { NumberObject, sumValues } from '../utils/number-object';
+import { startCase } from 'lodash-es';
+import { SVG } from '../carbon-estimation/carbon-estimation.constants';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class CarbonEstimationUtilService {
+ constructor() {}
+
+ public getOverallPercentageLabel = (emissions: NumberObject): string => {
+ const percentage = sumValues(emissions);
+ return this.getPercentageLabel(percentage);
+ };
+
+ public getPercentageLabel = (percentage: number): string => (percentage < 1 ? '<1%' : Math.round(percentage) + '%');
+
+ public getLabelAndSvg(key: string, parent: string = ''): { label: string; svg: string } {
+ switch (key) {
+ case 'software':
+ return { label: 'Software - Off the Shelf', svg: SVG.WEB };
+ case 'saas':
+ return { label: 'SaaS', svg: SVG.WEB };
+ case 'employee':
+ return { label: this.getEmployeeLabel(parent), svg: SVG.DEVICES };
+ case 'endUser':
+ return { label: 'End-User Devices', svg: SVG.DEVICES };
+ case 'network':
+ return { label: this.getNetworkLabel(parent), svg: SVG.ROUTER };
+ case 'server':
+ return { label: this.getServerLabel(parent), svg: SVG.STORAGE };
+ case 'managed':
+ return { label: 'Managed Services', svg: SVG.STORAGE };
+ case 'cloud':
+ return { label: 'Cloud Services', svg: SVG.CLOUD };
+ case 'networkTransfer':
+ return { label: 'Network Data Transfer', svg: SVG.CELL_TOWER };
+ default:
+ return { label: startCase(key), svg: '' };
+ }
+ }
+
+ private getEmployeeLabel(key: string): string {
+ switch (key) {
+ case 'Upstream Emissions':
+ return 'Employee Hardware';
+ case 'Direct Emissions':
+ return 'Employee Devices';
+ default:
+ return startCase(key);
+ }
+ }
+
+ private getNetworkLabel(key: string): string {
+ switch (key) {
+ case 'Upstream Emissions':
+ return 'Networking and Infrastructure Hardware';
+ case 'Direct Emissions':
+ return 'Networking and Infrastructure';
+ default:
+ return startCase(key);
+ }
+ }
+
+ private getServerLabel(key: string): string {
+ switch (key) {
+ case 'Upstream Emissions':
+ return 'Servers and Storage Hardware';
+ case 'Direct Emissions':
+ return 'Servers and Storage';
+ default:
+ return startCase(key);
+ }
+ }
+}
From 2bd22a5783d59aab4c979da9dd0a7f03ecfa5e3d Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Wed, 17 Jul 2024 15:47:31 +0100
Subject: [PATCH 05/37] Create table view for emissions data
---
.../carbon-estimation-table.component.html | 41 ++++++
.../carbon-estimation-table.component.spec.ts | 76 ++++++++++
.../carbon-estimation-table.component.ts | 130 ++++++++++++++++++
.../carbon-estimation.constants.ts | 3 +
4 files changed, 250 insertions(+)
create mode 100644 src/app/carbon-estimation-table/carbon-estimation-table.component.html
create mode 100644 src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
create mode 100644 src/app/carbon-estimation-table/carbon-estimation-table.component.ts
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
new file mode 100644
index 00000000..ec114c9a
--- /dev/null
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -0,0 +1,41 @@
+
+
+
+ Category |
+ Emissions |
+
+
+
+ @for (emissionsItem of emissions; track $index) {
+
+ @if (!emissionsItem.parent) {
+
+
+ {{ emissionsItem.category }}
+ |
+ {{ emissionsItem.emissions }} |
+ } @else if (emissionsItem.display) {
+
+
+
+ {{ emissionsItem.category }}
+
+ |
+
+ {{ emissionsItem.emissions }}
+ |
+ }
+
+ }
+
+
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
new file mode 100644
index 00000000..e88bc210
--- /dev/null
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
@@ -0,0 +1,76 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CarbonEstimationTableComponent } from './carbon-estimation-table.component';
+import { CarbonEstimationUtilService } from '../services/carbon-estimation-util.service';
+import { CarbonEstimation } from '../types/carbon-estimator';
+
+describe('CarbonEstimationTableComponent', () => {
+ let component: CarbonEstimationTableComponent;
+ let fixture: ComponentFixture;
+ const utilSpy = jasmine.createSpyObj('CarbonEstimationUtilService', [
+ 'getOverallPercentageLabel, getPercentageLabel, getLabelAndSvg',
+ ]);
+
+ utilSpy.getOverallPercentageLabel.and.returnValue('7%');
+ utilSpy.getPercentageLabel.and.returnValue('7%');
+ utilSpy.getLabelAndSvg.and.returnValue({ label: 'Emissions', svg: 'svg' });
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [CarbonEstimationTableComponent],
+ providers: [{ provide: CarbonEstimationUtilService, useValue: utilSpy }],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(CarbonEstimationTableComponent);
+ component = fixture.componentInstance;
+ const carbonEstimation: CarbonEstimation = {
+ version: '1.0',
+ upstreamEmissions: {
+ software: 7,
+ employee: 6,
+ network: 6,
+ server: 6,
+ },
+ directEmissions: {
+ employee: 9,
+ network: 8,
+ server: 8,
+ },
+ indirectEmissions: {
+ cloud: 9,
+ saas: 8,
+ managed: 8,
+ },
+ downstreamEmissions: {
+ endUser: 13,
+ networkTransfer: 12,
+ },
+ };
+
+ fixture.componentRef.setInput('carbonEstimation', carbonEstimation);
+ fixture.detectChanges();
+ });
+
+ it('should toggle display value of child emissions when toggle called', () => {
+ component.toggle('Upstream');
+ expect(component.emissions[0].display).toBeFalse();
+ component.emissions.forEach(emission => {
+ if (emission.parent === 'Upstream') {
+ expect(emission.display).toBeFalse();
+ }
+ });
+ });
+
+ it('should set child emissions to display by default', () => {
+ component.emissions.forEach(emission => {
+ if (emission.parent) {
+ expect(emission.display).toBeFalse();
+ }
+ });
+ });
+
+ it('should get emissions when getEmissions called', () => {
+ const emissions = component.getEmissions(component.carbonEstimation());
+ expect(emissions.length).toBe(13);
+ });
+});
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
new file mode 100644
index 00000000..2bb5f999
--- /dev/null
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -0,0 +1,130 @@
+import { Component, effect, input } from '@angular/core';
+import { CarbonEstimation } from '../types/carbon-estimator';
+import { EmissionsColours, EmissionsLabels } from '../carbon-estimation/carbon-estimation.constants';
+import { CarbonEstimationUtilService } from '../services/carbon-estimation-util.service';
+import { NumberObject } from '../utils/number-object';
+import { NgClass, NgStyle } from '@angular/common';
+
+type TableItem = {
+ category: string;
+ emissions: string;
+ parent?: string;
+ svg?: string;
+ colour: ItemColour;
+ display?: boolean;
+};
+
+type ItemColour = {
+ svg?: string;
+ background: string;
+};
+
+@Component({
+ selector: 'carbon-estimation-table',
+ standalone: true,
+ imports: [NgStyle, NgClass],
+ templateUrl: './carbon-estimation-table.component.html',
+})
+export class CarbonEstimationTableComponent {
+ public carbonEstimation = input.required();
+ public emissions: TableItem[] = [];
+
+ public expanded: { [key: string]: boolean } = {
+ [EmissionsLabels.Upstream]: true,
+ [EmissionsLabels.Direct]: true,
+ [EmissionsLabels.Indirect]: true,
+ [EmissionsLabels.Downstream]: true,
+ };
+
+ constructor(private carbonEstimationUtilService: CarbonEstimationUtilService) {
+ effect(() => {
+ this.emissions = this.getEmissions(this.carbonEstimation());
+ });
+ }
+
+ public toggle(category: string): void {
+ this.emissions.forEach(emission => {
+ if (emission.parent === category) {
+ emission.display = !emission.display;
+ }
+ });
+ this.expanded[category] = !this.expanded[category];
+ }
+
+ public getEmissions(carbonEstimation: CarbonEstimation): TableItem[] {
+ return [
+ {
+ category: EmissionsLabels.Upstream,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.upstreamEmissions),
+ colour: { background: EmissionsColours.Upstream },
+ },
+ ...this.getEmissionsBreakdown(
+ carbonEstimation.upstreamEmissions,
+ EmissionsLabels.Upstream,
+ EmissionsColours.Upstream,
+ EmissionsColours.UpstreamLight
+ ),
+ {
+ category: EmissionsLabels.Direct,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.directEmissions),
+ colour: { background: EmissionsColours.Direct },
+ },
+ ...this.getEmissionsBreakdown(
+ carbonEstimation.directEmissions,
+ EmissionsLabels.Direct,
+ EmissionsColours.Direct,
+ EmissionsColours.OperationLight
+ ),
+ {
+ category: EmissionsLabels.Indirect,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.indirectEmissions),
+ colour: { background: EmissionsColours.Indirect },
+ },
+ ...this.getEmissionsBreakdown(
+ carbonEstimation.indirectEmissions,
+ EmissionsLabels.Indirect,
+ EmissionsColours.Indirect,
+ EmissionsColours.OperationLight
+ ),
+ {
+ category: EmissionsLabels.Downstream,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.downstreamEmissions),
+ colour: { background: EmissionsColours.Downstream },
+ },
+ ...this.getEmissionsBreakdown(
+ carbonEstimation.downstreamEmissions,
+ EmissionsLabels.Downstream,
+ EmissionsColours.Downstream,
+ EmissionsColours.DownstreamLight
+ ),
+ ];
+ }
+
+ private getEmissionsBreakdown(
+ emissions: NumberObject,
+ parent: string,
+ svgColour: string,
+ backgroundColour: string
+ ): TableItem[] {
+ return (
+ Object.entries(emissions)
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ .filter(([_key, value]) => value !== 0)
+ .map(([_key, value]) =>
+ this.getTableItem(_key, value, parent, { background: backgroundColour, svg: svgColour })
+ )
+ );
+ }
+
+ private getTableItem(key: string, value: number, parent: string, colour: ItemColour): TableItem {
+ const { label, svg } = this.carbonEstimationUtilService.getLabelAndSvg(key, parent);
+ return {
+ category: label,
+ emissions: this.carbonEstimationUtilService.getPercentageLabel(value),
+ parent,
+ svg,
+ colour,
+ display: true,
+ };
+ }
+}
diff --git a/src/app/carbon-estimation/carbon-estimation.constants.ts b/src/app/carbon-estimation/carbon-estimation.constants.ts
index b34da436..9be3a1a5 100644
--- a/src/app/carbon-estimation/carbon-estimation.constants.ts
+++ b/src/app/carbon-estimation/carbon-estimation.constants.ts
@@ -2,9 +2,12 @@ import { ChartOptions } from '../types/carbon-estimator';
export enum EmissionsColours {
Upstream = '#40798C',
+ UpstreamLight = '#bfdae2',
Direct = '#CB3775',
Indirect = '#91234C',
+ OperationLight = '#f2afd1',
Downstream = '#4B7E56',
+ DownstreamLight = '#c1d9c3',
}
export enum PlaceholderEmissionsColours {
From b875d03ca3362977cdbd794ef7d42726d607e7d3 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Wed, 17 Jul 2024 16:09:00 +0100
Subject: [PATCH 06/37] Remove unused css file
---
.../carbon-estimation-treemap.component.css | 0
.../carbon-estimation-treemap.component.ts | 1 -
2 files changed, 1 deletion(-)
delete mode 100644 src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.css
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.css b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.css
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
index e58bda3d..c11d60c3 100644
--- a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
@@ -23,7 +23,6 @@ type ApexChartSeries = {
standalone: true,
imports: [NgApexchartsModule],
templateUrl: './carbon-estimation-treemap.component.html',
- styleUrl: './carbon-estimation-treemap.component.css',
})
export class CarbonEstimationTreemapComponent implements OnInit {
public carbonEstimation = input.required();
From e12afa4dc93ff3f5f74ef96add25bd7fc75bb7a9 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Wed, 17 Jul 2024 16:09:12 +0100
Subject: [PATCH 07/37] Add treemap unit tests
---
...arbon-estimation-treemap.component.spec.ts | 296 +++++++++++++++++-
1 file changed, 290 insertions(+), 6 deletions(-)
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
index 5e930058..c9feae59 100644
--- a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
@@ -1,6 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CarbonEstimationTreemapComponent } from './carbon-estimation-treemap.component';
+import { CarbonEstimation } from '../types/carbon-estimator';
describe('CarbonEstimationTreemapComponent', () => {
let component: CarbonEstimationTreemapComponent;
@@ -8,16 +9,299 @@ describe('CarbonEstimationTreemapComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [CarbonEstimationTreemapComponent]
- })
- .compileComponents();
-
+ imports: [CarbonEstimationTreemapComponent],
+ }).compileComponents();
+
fixture = TestBed.createComponent(CarbonEstimationTreemapComponent);
component = fixture.componentInstance;
+
+ const carbonEstimation: CarbonEstimation = {
+ version: '1.0',
+ upstreamEmissions: {
+ software: 7,
+ employee: 6,
+ network: 6,
+ server: 6,
+ },
+ directEmissions: {
+ employee: 9,
+ network: 8,
+ server: 8,
+ },
+ indirectEmissions: {
+ cloud: 9,
+ saas: 8,
+ managed: 8,
+ },
+ downstreamEmissions: {
+ endUser: 13,
+ networkTransfer: 12,
+ },
+ };
+
+ fixture.componentRef.setInput('carbonEstimation', carbonEstimation);
+ fixture.componentRef.setInput('chartHeight', 700);
+
+ fixture.detectChanges();
+ });
+
+ it('should set emissions with total % and category breakdown', () => {
+ const expectedEmissions = [
+ {
+ name: 'Upstream Emissions - 25%',
+ color: '#40798C',
+ data: [
+ {
+ x: 'Software - Off the Shelf',
+ y: 7,
+ meta: { svg: 'web-logo', parent: 'Upstream Emissions' },
+ },
+ {
+ x: 'Employee Hardware',
+ y: 6,
+ meta: { svg: 'devices-logo', parent: 'Upstream Emissions' },
+ },
+ {
+ x: 'Networking and Infrastructure Hardware',
+ y: 6,
+ meta: { svg: 'router-logo', parent: 'Upstream Emissions' },
+ },
+ {
+ x: 'Servers and Storage Hardware',
+ y: 6,
+ meta: { svg: 'storage-logo', parent: 'Upstream Emissions' },
+ },
+ ],
+ },
+ {
+ name: 'Direct Emissions - 25%',
+ color: '#CB3775',
+ data: [
+ {
+ x: 'Employee Devices',
+ y: 9,
+ meta: { svg: 'devices-logo', parent: 'Direct Emissions' },
+ },
+ {
+ x: 'Networking and Infrastructure',
+ y: 8,
+ meta: { svg: 'router-logo', parent: 'Direct Emissions' },
+ },
+ {
+ x: 'Servers and Storage',
+ y: 8,
+ meta: { svg: 'storage-logo', parent: 'Direct Emissions' },
+ },
+ ],
+ },
+ {
+ name: 'Indirect Emissions - 25%',
+ color: '#91234C',
+ data: [
+ {
+ x: 'Cloud Services',
+ y: 9,
+ meta: { svg: 'cloud-logo', parent: 'Indirect Emissions' },
+ },
+ {
+ x: 'SaaS',
+ y: 8,
+ meta: { svg: 'web-logo', parent: 'Indirect Emissions' },
+ },
+ {
+ x: 'Managed Services',
+ y: 8,
+ meta: { svg: 'storage-logo', parent: 'Indirect Emissions' },
+ },
+ ],
+ },
+ {
+ name: 'Downstream Emissions - 25%',
+ color: '#4B7E56',
+ data: [
+ {
+ x: 'End-User Devices',
+ y: 13,
+ meta: { svg: 'devices-logo', parent: 'Downstream Emissions' },
+ },
+ {
+ x: 'Network Data Transfer',
+ y: 12,
+ meta: { svg: 'cell-tower-logo', parent: 'Downstream Emissions' },
+ },
+ ],
+ },
+ ];
+
+ expect(component.emissions).toEqual(expectedEmissions);
+ });
+
+ it('should have detailed aria label', () => {
+ expect(component.emissionAriaLabel.length).toBeGreaterThan(25);
+ });
+
+ it('should set label to <1% if emission is less than 1', () => {
+ const carbonEstimation: CarbonEstimation = {
+ version: '1.0',
+ upstreamEmissions: {
+ software: 0.2,
+ employee: 0.1,
+ network: 0.1,
+ server: 0.1,
+ },
+ directEmissions: {
+ employee: 34.5,
+ network: 8,
+ server: 8,
+ },
+ indirectEmissions: {
+ cloud: 9,
+ saas: 8,
+ managed: 8,
+ },
+ downstreamEmissions: {
+ endUser: 13,
+ networkTransfer: 12,
+ },
+ };
+ fixture.componentRef.setInput('carbonEstimation', carbonEstimation);
+
+ fixture.detectChanges();
+
+ expect(component.emissions[0].name).toBe('Upstream Emissions - <1%');
+ });
+
+ it('should remove categories when they are 0', () => {
+ const carbonEstimation: CarbonEstimation = {
+ version: '1.0',
+ upstreamEmissions: {
+ software: 25,
+ employee: 0,
+ network: 0,
+ server: 0,
+ },
+ directEmissions: {
+ employee: 25,
+ network: 0,
+ server: 0,
+ },
+ indirectEmissions: {
+ cloud: 25,
+ saas: 0,
+ managed: 0,
+ },
+ downstreamEmissions: {
+ endUser: 25,
+ networkTransfer: 0,
+ },
+ };
+ fixture.componentRef.setInput('carbonEstimation', carbonEstimation);
+
fixture.detectChanges();
+
+ const expectedEmissions = [
+ {
+ name: 'Upstream Emissions - 25%',
+ color: '#40798C',
+ data: [
+ {
+ x: 'Software - Off the Shelf',
+ y: 25,
+ meta: { svg: 'web-logo', parent: 'Upstream Emissions' },
+ },
+ ],
+ },
+ {
+ name: 'Direct Emissions - 25%',
+ color: '#CB3775',
+ data: [
+ {
+ x: 'Employee Devices',
+ y: 25,
+ meta: { svg: 'devices-logo', parent: 'Direct Emissions' },
+ },
+ ],
+ },
+ {
+ name: 'Indirect Emissions - 25%',
+ color: '#91234C',
+ data: [
+ {
+ x: 'Cloud Services',
+ y: 25,
+ meta: { svg: 'cloud-logo', parent: 'Indirect Emissions' },
+ },
+ ],
+ },
+ {
+ name: 'Downstream Emissions - 25%',
+ color: '#4B7E56',
+ data: [
+ {
+ x: 'End-User Devices',
+ y: 25,
+ meta: { svg: 'devices-logo', parent: 'Downstream Emissions' },
+ },
+ ],
+ },
+ ];
+
+ expect(component.emissions).toEqual(expectedEmissions);
});
- it('should create', () => {
- expect(component).toBeTruthy();
+ it('should remove parent categories when all values are 0', () => {
+ const carbonEstimation: CarbonEstimation = {
+ version: '1.0',
+ upstreamEmissions: {
+ software: 50,
+ employee: 0,
+ network: 0,
+ server: 0,
+ },
+ directEmissions: {
+ employee: 50,
+ network: 0,
+ server: 0,
+ },
+ indirectEmissions: {
+ cloud: 0,
+ saas: 0,
+ managed: 0,
+ },
+ downstreamEmissions: {
+ endUser: 0,
+ networkTransfer: 0,
+ },
+ };
+ fixture.componentRef.setInput('carbonEstimation', carbonEstimation);
+
+ fixture.detectChanges();
+
+ const expectedEmissions = [
+ {
+ name: 'Upstream Emissions - 50%',
+ color: '#40798C',
+ data: [
+ {
+ x: 'Software - Off the Shelf',
+ y: 50,
+ meta: { svg: 'web-logo', parent: 'Upstream Emissions' },
+ },
+ ],
+ },
+ {
+ name: 'Direct Emissions - 50%',
+ color: '#CB3775',
+ data: [
+ {
+ x: 'Employee Devices',
+ y: 50,
+ meta: { svg: 'devices-logo', parent: 'Direct Emissions' },
+ },
+ ],
+ },
+ ];
+
+ expect(component.emissions).toEqual(expectedEmissions);
});
});
From 7647138c551cc3a7ff5e84086dbbe1e49e23cc27 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Wed, 17 Jul 2024 16:10:11 +0100
Subject: [PATCH 08/37] Refactor carbon-estimation to use new treemap and table
components
---
.../carbon-estimation-treemap.component.html | 18 +-
.../carbon-estimation-treemap.component.ts | 40 ++-
.../carbon-estimation.component.html | 22 +-
.../carbon-estimation.component.spec.ts | 293 +-----------------
.../carbon-estimation.component.ts | 195 ++----------
.../carbon-estimation.constants.ts | 31 +-
6 files changed, 91 insertions(+), 508 deletions(-)
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.html b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.html
index 245f5c95..e78a293d 100644
--- a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.html
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.html
@@ -1,12 +1,12 @@
-
+ [series]="chartData()"
+ [chart]="chartOptions().chart"
+ [plotOptions]="chartOptions().plotOptions"
+ [legend]="chartOptions().legend"
+ [states]="chartOptions().states"
+ [dataLabels]="chartOptions().dataLabels"
+ [tooltip]="chartOptions().tooltip">
+
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
index c11d60c3..5cf1e376 100644
--- a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.ts
@@ -1,11 +1,12 @@
-import { Component, OnInit, ViewChild, effect, input } from '@angular/core';
+import { Component, ViewChild, computed, effect, input } from '@angular/core';
import { ApexAxisChartSeries, ChartComponent, NgApexchartsModule } from 'ng-apexcharts';
-import { CarbonEstimation, ChartOptions } from '../types/carbon-estimator';
+import { CarbonEstimation } from '../types/carbon-estimator';
import {
- chartOptions,
tooltipFormatter,
EmissionsColours,
EmissionsLabels,
+ getBaseChartOptions,
+ placeholderData,
} from '../carbon-estimation/carbon-estimation.constants';
import { NumberObject } from '../utils/number-object';
import { CarbonEstimationUtilService } from '../services/carbon-estimation-util.service';
@@ -24,34 +25,35 @@ type ApexChartSeries = {
imports: [NgApexchartsModule],
templateUrl: './carbon-estimation-treemap.component.html',
})
-export class CarbonEstimationTreemapComponent implements OnInit {
- public carbonEstimation = input.required();
+export class CarbonEstimationTreemapComponent {
+ public carbonEstimation = input();
public chartHeight = input.required();
- public emissions: ApexAxisChartSeries = [];
- public emissionAriaLabel = 'Estimations of emissions.';
+ public chartData = computed(() => this.getChartData(this.carbonEstimation()));
+ public emissionAriaLabel = computed(() => this.getAriaLabel(this.chartData(), !this.carbonEstimation()));
- public chartOptions: ChartOptions = chartOptions;
+ public chartOptions = computed(() => this.getChartOptions(!this.carbonEstimation()));
private tooltipFormatter = tooltipFormatter;
@ViewChild('chart') chart: ChartComponent | undefined;
constructor(private carbonEstimationUtilService: CarbonEstimationUtilService) {
effect(() => {
- this.emissions = this.getOverallEmissionPercentages(this.carbonEstimation());
- this.emissionAriaLabel = this.getAriaLabel(this.emissions);
const chartHeight = this.chartHeight();
- if (chartHeight !== this.chartOptions.chart.height) {
+ if (chartHeight !== this.chartOptions().chart.height) {
this.chart?.updateOptions({ chart: { height: chartHeight } });
}
});
}
- public ngOnInit(): void {
- const chartHeight = this.chartHeight();
- if (chartHeight > 0) {
- this.chartOptions.chart.height = chartHeight;
- }
+ private getChartOptions(isPlaceholder: boolean) {
+ const chartOptions = getBaseChartOptions(isPlaceholder);
+ chartOptions.chart.height = this.chartHeight();
+ return chartOptions;
+ }
+
+ private getChartData(estimation?: CarbonEstimation): ApexAxisChartSeries {
+ return estimation ? this.getOverallEmissionPercentages(estimation) : placeholderData;
}
private getOverallEmissionPercentages(carbonEstimation: CarbonEstimation): ApexAxisChartSeries {
@@ -79,7 +81,11 @@ export class CarbonEstimationTreemapComponent implements OnInit {
].filter(entry => entry.data.length !== 0);
}
- private getAriaLabel(emission: ApexAxisChartSeries): string {
+ private getAriaLabel(chartData: ApexAxisChartSeries, isPlaceholder: boolean) {
+ return isPlaceholder ? 'Placeholder for estimation of emissions' : this.getEmissionAriaLabel(chartData);
+ }
+
+ private getEmissionAriaLabel(emission: ApexAxisChartSeries): string {
return `Estimation of emissions. ${emission.map(entry => this.getAriaLabelForCategory(entry as ApexChartSeries)).join(' ')}`;
}
diff --git a/src/app/carbon-estimation/carbon-estimation.component.html b/src/app/carbon-estimation/carbon-estimation.component.html
index 2e28eaba..b19aaabb 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.html
+++ b/src/app/carbon-estimation/carbon-estimation.component.html
@@ -21,16 +21,14 @@ Estimations
-
+
+
+
+
+
+
+
diff --git a/src/app/carbon-estimation/carbon-estimation.component.spec.ts b/src/app/carbon-estimation/carbon-estimation.component.spec.ts
index 15d38e03..fc3dc84c 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.spec.ts
+++ b/src/app/carbon-estimation/carbon-estimation.component.spec.ts
@@ -2,7 +2,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CarbonEstimationComponent } from './carbon-estimation.component';
import { CarbonEstimation } from '../types/carbon-estimator';
-import { ChartComponent } from 'ng-apexcharts';
import { sumValues } from '../utils/number-object';
import { estimatorHeights } from './carbon-estimation.constants';
@@ -51,76 +50,58 @@ describe('CarbonEstimationComponent', () => {
// Check that the height is set to a positive value.
// (Checking for a specific value would require spying on the window object before
// the test starts for consistent local and CI/CD results.)
- expect(component.chartOptions().chart.height).toBeGreaterThan(0);
+ expect(component.chartHeight).toBeGreaterThan(0);
});
it('should subtract the extraHeight input from the chart height on laptop screens', () => {
- spyOn(component.chart as ChartComponent, 'updateOptions');
spyOnProperty(component.detailsPanel.nativeElement, 'clientHeight').and.returnValue(200);
fixture.componentRef.setInput('extraHeight', '100');
component.onResize(1500, 1500, 2000);
- expect(component.chart?.updateOptions).toHaveBeenCalledOnceWith({
- chart: { height: 1500 - estimatorBaseHeight - 200 - 100 },
- });
+ expect(component.chartHeight).toBe(1500 - estimatorBaseHeight - 200 - 100);
});
it('should recalculate chart height on window resize, for laptop screen', () => {
- spyOn(component.chart as ChartComponent, 'updateOptions');
spyOnProperty(component.detailsPanel.nativeElement, 'clientHeight').and.returnValue(200);
component.onResize(1500, 2000, 2000);
- expect(component.chart?.updateOptions).toHaveBeenCalledOnceWith({
- chart: { height: 1500 - estimatorBaseHeight - 200 },
- });
+ expect(component.chartHeight).toBe(1500 - estimatorBaseHeight - 200);
});
it('should recalculate chart height on window resize, for mobile screen', () => {
- spyOn(component.chart as ChartComponent, 'updateOptions');
spyOnProperty(component.detailsPanel.nativeElement, 'clientHeight').and.returnValue(200);
component.onResize(1000, 500, 1000);
- expect(component.chart?.updateOptions).toHaveBeenCalledOnceWith({
- chart: { height: 1000 - estimatorBaseHeight - 200 + estimatorHeights.title },
- });
+ expect(component.chartHeight).toBe(1000 - estimatorBaseHeight - 200 + estimatorHeights.title);
});
it('should cap chart height as a percentage of screen height, for laptop screen', () => {
- spyOn(component.chart as ChartComponent, 'updateOptions');
spyOnProperty(component.detailsPanel.nativeElement, 'clientHeight').and.returnValue(200);
const screenHeight = 2000;
component.onResize(2000, 1500, screenHeight);
- expect(component.chart?.updateOptions).toHaveBeenCalledOnceWith({
- chart: { height: screenHeight * 0.75 },
- });
+ expect(component.chartHeight).toBe(screenHeight * 0.75);
});
it('should cap chart height as a percentage of screen height, for mobile screen', () => {
- spyOn(component.chart as ChartComponent, 'updateOptions');
spyOnProperty(component.detailsPanel.nativeElement, 'clientHeight').and.returnValue(200);
const screenHeight = 1200;
component.onResize(1200, 500, screenHeight);
- expect(component.chart?.updateOptions).toHaveBeenCalledOnceWith({
- chart: { height: screenHeight * 0.75 },
- });
+ expect(component.chartHeight).toBe(screenHeight * 0.75);
});
it('should have a chart height of 300 for small innerHeight values (if screen height is large enough)', () => {
- spyOn(component.chart as ChartComponent, 'updateOptions');
spyOnProperty(component.detailsPanel.nativeElement, 'clientHeight').and.returnValue(200);
component.onResize(100, 1000, 2000);
- expect(component.chart?.updateOptions).toHaveBeenCalledOnceWith({
- chart: { height: 300 },
- });
+ expect(component.chartHeight).toBe(300);
});
it('should call onResize when onExpansion is called', () => {
@@ -130,264 +111,4 @@ describe('CarbonEstimationComponent', () => {
expect(component.onResize).toHaveBeenCalledTimes(1);
});
-
- it('should set emissions with total % and category breakdown', () => {
- const expectedEmissions = [
- {
- name: 'Upstream Emissions - 25%',
- color: '#40798C',
- data: [
- {
- x: 'Software - Off the Shelf',
- y: 7,
- meta: { svg: 'web-logo', parent: 'Upstream Emissions' },
- },
- {
- x: 'Employee Hardware',
- y: 6,
- meta: { svg: 'devices-logo', parent: 'Upstream Emissions' },
- },
- {
- x: 'Networking and Infrastructure Hardware',
- y: 6,
- meta: { svg: 'router-logo', parent: 'Upstream Emissions' },
- },
- {
- x: 'Servers and Storage Hardware',
- y: 6,
- meta: { svg: 'storage-logo', parent: 'Upstream Emissions' },
- },
- ],
- },
- {
- name: 'Direct Emissions - 25%',
- color: '#CB3775',
- data: [
- {
- x: 'Employee Devices',
- y: 9,
- meta: { svg: 'devices-logo', parent: 'Direct Emissions' },
- },
- {
- x: 'Networking and Infrastructure',
- y: 8,
- meta: { svg: 'router-logo', parent: 'Direct Emissions' },
- },
- {
- x: 'Servers and Storage',
- y: 8,
- meta: { svg: 'storage-logo', parent: 'Direct Emissions' },
- },
- ],
- },
- {
- name: 'Indirect Emissions - 25%',
- color: '#91234C',
- data: [
- {
- x: 'Cloud Services',
- y: 9,
- meta: { svg: 'cloud-logo', parent: 'Indirect Emissions' },
- },
- {
- x: 'SaaS',
- y: 8,
- meta: { svg: 'web-logo', parent: 'Indirect Emissions' },
- },
- {
- x: 'Managed Services',
- y: 8,
- meta: { svg: 'storage-logo', parent: 'Indirect Emissions' },
- },
- ],
- },
- {
- name: 'Downstream Emissions - 25%',
- color: '#4B7E56',
- data: [
- {
- x: 'End-User Devices',
- y: 13,
- meta: { svg: 'devices-logo', parent: 'Downstream Emissions' },
- },
- {
- x: 'Network Data Transfer',
- y: 12,
- meta: { svg: 'cell-tower-logo', parent: 'Downstream Emissions' },
- },
- ],
- },
- ];
-
- expect(component.chartData()).toEqual(expectedEmissions);
- });
-
- it('should have detailed aria label', () => {
- expect(component.emissionAriaLabel().length).toBeGreaterThan(25);
- });
-
- it('should set label to <1% if emission is less than 1', () => {
- const carbonEstimation: CarbonEstimation = {
- version: '1.0',
- upstreamEmissions: {
- software: 0.2,
- employee: 0.1,
- network: 0.1,
- server: 0.1,
- },
- directEmissions: {
- employee: 34.5,
- network: 8,
- server: 8,
- },
- indirectEmissions: {
- cloud: 9,
- saas: 8,
- managed: 8,
- },
- downstreamEmissions: {
- endUser: 13,
- networkTransfer: 12,
- },
- };
- fixture.componentRef.setInput('carbonEstimation', carbonEstimation);
-
- fixture.detectChanges();
-
- expect(component.chartData()[0].name).toBe('Upstream Emissions - <1%');
- });
-
- it('should remove categories when they are 0', () => {
- const carbonEstimation: CarbonEstimation = {
- version: '1.0',
- upstreamEmissions: {
- software: 25,
- employee: 0,
- network: 0,
- server: 0,
- },
- directEmissions: {
- employee: 25,
- network: 0,
- server: 0,
- },
- indirectEmissions: {
- cloud: 25,
- saas: 0,
- managed: 0,
- },
- downstreamEmissions: {
- endUser: 25,
- networkTransfer: 0,
- },
- };
- fixture.componentRef.setInput('carbonEstimation', carbonEstimation);
-
- fixture.detectChanges();
-
- const expectedEmissions = [
- {
- name: 'Upstream Emissions - 25%',
- color: '#40798C',
- data: [
- {
- x: 'Software - Off the Shelf',
- y: 25,
- meta: { svg: 'web-logo', parent: 'Upstream Emissions' },
- },
- ],
- },
- {
- name: 'Direct Emissions - 25%',
- color: '#CB3775',
- data: [
- {
- x: 'Employee Devices',
- y: 25,
- meta: { svg: 'devices-logo', parent: 'Direct Emissions' },
- },
- ],
- },
- {
- name: 'Indirect Emissions - 25%',
- color: '#91234C',
- data: [
- {
- x: 'Cloud Services',
- y: 25,
- meta: { svg: 'cloud-logo', parent: 'Indirect Emissions' },
- },
- ],
- },
- {
- name: 'Downstream Emissions - 25%',
- color: '#4B7E56',
- data: [
- {
- x: 'End-User Devices',
- y: 25,
- meta: { svg: 'devices-logo', parent: 'Downstream Emissions' },
- },
- ],
- },
- ];
-
- expect(component.chartData()).toEqual(expectedEmissions);
- });
-
- it('should remove parent categories when all values are 0', () => {
- const carbonEstimation: CarbonEstimation = {
- version: '1.0',
- upstreamEmissions: {
- software: 50,
- employee: 0,
- network: 0,
- server: 0,
- },
- directEmissions: {
- employee: 50,
- network: 0,
- server: 0,
- },
- indirectEmissions: {
- cloud: 0,
- saas: 0,
- managed: 0,
- },
- downstreamEmissions: {
- endUser: 0,
- networkTransfer: 0,
- },
- };
- fixture.componentRef.setInput('carbonEstimation', carbonEstimation);
-
- fixture.detectChanges();
-
- const expectedEmissions = [
- {
- name: 'Upstream Emissions - 50%',
- color: '#40798C',
- data: [
- {
- x: 'Software - Off the Shelf',
- y: 50,
- meta: { svg: 'web-logo', parent: 'Upstream Emissions' },
- },
- ],
- },
- {
- name: 'Direct Emissions - 50%',
- color: '#CB3775',
- data: [
- {
- x: 'Employee Devices',
- y: 50,
- meta: { svg: 'devices-logo', parent: 'Direct Emissions' },
- },
- ],
- },
- ];
-
- expect(component.chartData()).toEqual(expectedEmissions);
- });
});
diff --git a/src/app/carbon-estimation/carbon-estimation.component.ts b/src/app/carbon-estimation/carbon-estimation.component.ts
index f25ea21e..10bf764a 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.ts
+++ b/src/app/carbon-estimation/carbon-estimation.component.ts
@@ -1,27 +1,24 @@
-import { ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, ViewChild, computed, input } from '@angular/core';
-import { CarbonEstimation } from '../types/carbon-estimator';
-import { NumberObject, sumValues } from '../utils/number-object';
-import { ApexAxisChartSeries, ChartComponent, NgApexchartsModule } from 'ng-apexcharts';
-
-import { startCase } from 'lodash-es';
-import {
- EmissionsColours,
- EmissionsLabels,
- SVG,
- getBaseChartOptions,
- estimatorHeights,
- tooltipFormatter,
- placeholderData,
- ApexChartSeriesItem,
- ApexChartDataItem,
-} from './carbon-estimation.constants';
+import { ChangeDetectorRef, Component, ElementRef, input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ExpansionPanelComponent } from '../expansion-panel/expansion-panel.component';
-import { Subscription, debounceTime, fromEvent } from 'rxjs';
+import { TabsComponent } from '../tab/tabs/tabs.component';
+import { TabItemComponent } from '../tab/tab-item/tab-item.component';
+import { CarbonEstimationTreemapComponent } from '../carbon-estimation-treemap/carbon-estimation-treemap.component';
+import { CarbonEstimation } from '../types/carbon-estimator';
+import { sumValues } from '../utils/number-object';
+import { estimatorHeights } from './carbon-estimation.constants';
+import { debounceTime, fromEvent, Subscription } from 'rxjs';
+import { CarbonEstimationTableComponent } from '../carbon-estimation-table/carbon-estimation-table.component';
@Component({
selector: 'carbon-estimation',
standalone: true,
- imports: [NgApexchartsModule, ExpansionPanelComponent],
+ imports: [
+ ExpansionPanelComponent,
+ TabsComponent,
+ TabItemComponent,
+ CarbonEstimationTreemapComponent,
+ CarbonEstimationTableComponent,
+ ],
templateUrl: './carbon-estimation.component.html',
styleUrls: ['./carbon-estimation.component.css'],
})
@@ -29,21 +26,18 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
public carbonEstimation = input();
public extraHeight = input();
- public chartData = computed(() => this.getChartData(this.carbonEstimation()));
- public emissionAriaLabel = computed(() => this.getEmissionAriaLabel(this.chartData(), !this.carbonEstimation()));
+ @ViewChild('detailsPanel', { static: true, read: ElementRef }) detailsPanel!: ElementRef;
- public chartOptions = computed(() => this.getChartOptions(!this.carbonEstimation()));
- private tooltipFormatter = tooltipFormatter;
- private estimatorBaseHeight = sumValues(estimatorHeights);
+ public chartHeight!: number;
+ private estimatorBaseHeight = sumValues(estimatorHeights);
private resizeSubscription!: Subscription;
- @ViewChild('chart') chart: ChartComponent | undefined;
- @ViewChild('detailsPanel', { static: true, read: ElementRef }) detailsPanel!: ElementRef;
-
constructor(private changeDetectorRef: ChangeDetectorRef) {}
public ngOnInit(): void {
+ this.chartHeight = this.getChartHeight(window.innerHeight, window.innerWidth, window.screen.height);
+
this.resizeSubscription = fromEvent(window, 'resize')
.pipe(debounceTime(500))
.subscribe(() => this.onResize(window.innerHeight, window.innerWidth, window.screen.height));
@@ -53,73 +47,13 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
this.resizeSubscription.unsubscribe();
}
- public onExpanded(): void {
- this.changeDetectorRef.detectChanges();
- this.onResize(window.innerHeight, window.innerWidth, window.screen.height);
- }
-
onResize(innerHeight: number, innerWidth: number, screenHeight: number): void {
- const chartHeight = this.getChartHeight(innerHeight, innerWidth, screenHeight);
- this.chart?.updateOptions({ chart: { height: chartHeight } });
- }
-
- private getOverallEmissionPercentages(carbonEstimation: CarbonEstimation): ApexAxisChartSeries {
- return [
- {
- name: `${EmissionsLabels.Upstream} - ${this.getOverallPercentageLabel(carbonEstimation.upstreamEmissions)}`,
- color: EmissionsColours.Upstream,
- data: this.getEmissionPercentages(carbonEstimation.upstreamEmissions, EmissionsLabels.Upstream),
- },
- {
- name: `${EmissionsLabels.Direct} - ${this.getOverallPercentageLabel(carbonEstimation.directEmissions)}`,
- color: EmissionsColours.Direct,
- data: this.getEmissionPercentages(carbonEstimation.directEmissions, EmissionsLabels.Direct),
- },
- {
- name: `${EmissionsLabels.Indirect} - ${this.getOverallPercentageLabel(carbonEstimation.indirectEmissions)}`,
- color: EmissionsColours.Indirect,
- data: this.getEmissionPercentages(carbonEstimation.indirectEmissions, EmissionsLabels.Indirect),
- },
- {
- name: `${EmissionsLabels.Downstream} - ${this.getOverallPercentageLabel(carbonEstimation.downstreamEmissions)}`,
- color: EmissionsColours.Downstream,
- data: this.getEmissionPercentages(carbonEstimation.downstreamEmissions, EmissionsLabels.Downstream),
- },
- ].filter(entry => entry.data.length !== 0);
- }
-
- private getAriaLabel(emission: ApexAxisChartSeries): string {
- return `Estimation of emissions. ${emission.map(entry => this.getAriaLabelForCategory(entry as ApexChartSeriesItem)).join(' ')}`;
- }
-
- private getEmissionAriaLabel(chartData: ApexAxisChartSeries, isPlaceholder: boolean) {
- return isPlaceholder ? 'Placeholder for estimation of emissions' : this.getAriaLabel(chartData);
- }
-
- private getAriaLabelForCategory(series: ApexChartSeriesItem): string {
- const category = series.name.replace('-', 'are');
- return `${category}${this.getEmissionMadeUp(series.data)}`;
- }
-
- private getEmissionMadeUp(emission: ApexChartDataItem[]): string {
- if (emission.length === 0) {
- return '.';
- }
- return `, made up of ${emission.map(item => `${item.x} ${this.tooltipFormatter(item.y)}`).join(', ')}.`;
+ this.chartHeight = this.getChartHeight(innerHeight, innerWidth, screenHeight);
}
- private getOverallPercentageLabel = (emissions: NumberObject): string => {
- const percentage = sumValues(emissions);
- return percentage < 1 ? '<1%' : Math.round(percentage) + '%';
- };
-
- private getEmissionPercentages(emissions: NumberObject, parent: string): ApexChartDataItem[] {
- return (
- Object.entries(emissions)
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- .filter(([_key, value]) => value !== 0)
- .map(([_key, value]) => this.getDataItem(_key, value, parent))
- );
+ public onExpanded(): void {
+ this.changeDetectorRef.detectChanges();
+ this.onResize(window.innerHeight, window.innerWidth, window.screen.height);
}
private getChartHeight(innerHeight: number, innerWidth: number, screenHeight: number): number {
@@ -149,83 +83,4 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
return heightBoundedAboveAndBelow;
}
-
- private getChartOptions(isPlaceholder: boolean) {
- const chartOptions = getBaseChartOptions(isPlaceholder);
- chartOptions.chart.height = this.getChartHeight(window.innerHeight, window.innerWidth, window.screen.height);
- return chartOptions;
- }
-
- private getChartData(estimation?: CarbonEstimation): ApexAxisChartSeries {
- return estimation ? this.getOverallEmissionPercentages(estimation) : placeholderData;
- }
-
- private getDataItem(key: string, value: number, parent: string): ApexChartDataItem {
- switch (key) {
- case 'software':
- return this.getDataItemObject('Software - Off the Shelf', value, SVG.WEB, parent);
- case 'saas':
- return this.getDataItemObject('SaaS', value, SVG.WEB, parent);
- case 'employee':
- return this.getDataItemObject(this.getEmployeeLabel(parent), value, SVG.DEVICES, parent);
- case 'endUser':
- return this.getDataItemObject('End-User Devices', value, SVG.DEVICES, parent);
- case 'network':
- return this.getDataItemObject(this.getNetworkLabel(parent), value, SVG.ROUTER, parent);
- case 'server':
- return this.getDataItemObject(this.getServerLabel(parent), value, SVG.STORAGE, parent);
- case 'managed':
- return this.getDataItemObject('Managed Services', value, SVG.STORAGE, parent);
- case 'cloud':
- return this.getDataItemObject('Cloud Services', value, SVG.CLOUD, parent);
- case 'networkTransfer':
- return this.getDataItemObject('Network Data Transfer', value, SVG.CELL_TOWER, parent);
- default:
- return this.getDataItemObject(startCase(key), value, '', parent);
- }
- }
-
- private getDataItemObject(x: string, y: number, svg: string, parent: string): ApexChartDataItem {
- return {
- x,
- y,
- meta: {
- svg,
- parent,
- },
- };
- }
-
- private getEmployeeLabel(key: string): string {
- switch (key) {
- case 'Upstream Emissions':
- return 'Employee Hardware';
- case 'Direct Emissions':
- return 'Employee Devices';
- default:
- return startCase(key);
- }
- }
-
- private getNetworkLabel(key: string): string {
- switch (key) {
- case 'Upstream Emissions':
- return 'Networking and Infrastructure Hardware';
- case 'Direct Emissions':
- return 'Networking and Infrastructure';
- default:
- return startCase(key);
- }
- }
-
- private getServerLabel(key: string): string {
- switch (key) {
- case 'Upstream Emissions':
- return 'Servers and Storage Hardware';
- case 'Direct Emissions':
- return 'Servers and Storage';
- default:
- return startCase(key);
- }
- }
}
diff --git a/src/app/carbon-estimation/carbon-estimation.constants.ts b/src/app/carbon-estimation/carbon-estimation.constants.ts
index 9be3a1a5..ce4ac6bc 100644
--- a/src/app/carbon-estimation/carbon-estimation.constants.ts
+++ b/src/app/carbon-estimation/carbon-estimation.constants.ts
@@ -51,23 +51,26 @@ const getCustomTooltip = (isPlaceholder: boolean) => {
};
const getCustomDataLabel = (isPlaceholder: boolean) => {
- const customDataLabel = (value: string | number | number[], {
- seriesIndex,
- dataPointIndex,
- w,
- }: {
- seriesIndex: number;
- dataPointIndex: number;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- w: any;
- }) => {
+ const customDataLabel = (
+ value: string | number | number[],
+ {
+ seriesIndex,
+ dataPointIndex,
+ w,
+ }: {
+ seriesIndex: number;
+ dataPointIndex: number;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ w: any;
+ }
+ ) => {
const initialSeries = w.globals.initialSeries[seriesIndex];
const data = initialSeries.data[dataPointIndex];
-
- return `${value} - ${isPlaceholder ? '?' : tooltipFormatter(data.y)}`
- }
+
+ return `${value} - ${isPlaceholder ? '?' : tooltipFormatter(data.y)}`;
+ };
return customDataLabel;
-}
+};
export const getBaseChartOptions = (isPlaceholder: boolean) => {
const chartOptions: ChartOptions = {
From 5afdc9b62b22d608bad10f8b66027655148325c2 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Thu, 18 Jul 2024 14:46:29 +0100
Subject: [PATCH 09/37] Style tabs
---
.../carbon-estimation.component.html | 2 +-
src/app/tab/tab-item/tab-item.component.css | 0
src/app/tab/tab-item/tab-item.component.ts | 1 -
src/app/tab/tabs/tabs.component.css | 0
src/app/tab/tabs/tabs.component.html | 13 +++++++++----
src/sl-styles.css | 7 ++++++-
src/styles.css | 5 +++++
7 files changed, 21 insertions(+), 7 deletions(-)
delete mode 100644 src/app/tab/tab-item/tab-item.component.css
delete mode 100644 src/app/tab/tabs/tabs.component.css
diff --git a/src/app/carbon-estimation/carbon-estimation.component.html b/src/app/carbon-estimation/carbon-estimation.component.html
index b19aaabb..dd7ac551 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.html
+++ b/src/app/carbon-estimation/carbon-estimation.component.html
@@ -21,7 +21,7 @@ Estimations
-
+
+
@for (tab of tabs; track tab.title) {
-
+
{{ tab.title() }}
-
+
}
-
+
diff --git a/src/sl-styles.css b/src/sl-styles.css
index ac757069..f8a99951 100644
--- a/src/sl-styles.css
+++ b/src/sl-styles.css
@@ -32,4 +32,9 @@ input {
.tce-button-calculate:hover, .tce-button-reset:hover, .tce-button-assumptions:hover, .tce-button-close:hover {
background-color: var(--primary-turquoise);
@apply tce-text-white
-}
\ No newline at end of file
+}
+
+.tce-active-tab, .tce-active-tab:hover {
+ border-color: var(--primary-turquoise);
+ @apply tce-cursor-default
+}
diff --git a/src/styles.css b/src/styles.css
index bcd5f382..69c14da8 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -13,3 +13,8 @@
.tce-button-reset, .tce-button-assumptions {
@apply tce-bg-slate-200 tce-text-slate-800 hover:tce-bg-slate-300 tce-rounded
}
+
+
+.tce-active-tab, .tce-active-tab:hover {
+ @apply tce-cursor-default tce-border-sky-800
+}
From 39e8e2464f2bf7c90f6a91e7ac41316bd6084e2b Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Thu, 18 Jul 2024 14:47:03 +0100
Subject: [PATCH 10/37] make sure only ever 1 active tab
---
src/app/tab/tabs/tabs.component.ts | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/app/tab/tabs/tabs.component.ts b/src/app/tab/tabs/tabs.component.ts
index a7115583..1c217dc6 100644
--- a/src/app/tab/tabs/tabs.component.ts
+++ b/src/app/tab/tabs/tabs.component.ts
@@ -1,4 +1,4 @@
-import { Component, ContentChildren, QueryList } from '@angular/core';
+import { AfterContentInit, Component, ContentChildren, QueryList } from '@angular/core';
import { TabItemComponent } from '../tab-item/tab-item.component';
@Component({
@@ -6,12 +6,21 @@ import { TabItemComponent } from '../tab-item/tab-item.component';
standalone: true,
imports: [TabItemComponent],
templateUrl: './tabs.component.html',
- styleUrl: './tabs.component.css',
})
-export class TabsComponent {
+export class TabsComponent implements AfterContentInit {
@ContentChildren(TabItemComponent) tabs!: QueryList;
- selectTab(selectedTab: TabItemComponent) {
+ public ngAfterContentInit() {
+ const activeTabs = this.tabs.filter(tab => tab.active());
+
+ if (activeTabs.length === 0) {
+ this.selectTab(this.tabs.first);
+ } else if (activeTabs.length > 1) {
+ this.selectTab(activeTabs[0]);
+ }
+ }
+
+ public selectTab(selectedTab: TabItemComponent) {
this.tabs.filter(tab => tab.active()).forEach(tab => tab.active.set(false));
selectedTab.active.set(true);
}
From 24d947ca26e004682c78f898d20b8a208a5c9052 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Thu, 18 Jul 2024 14:47:22 +0100
Subject: [PATCH 11/37] add tab unit tests
---
package-lock.json | 16 +++++++++
package.json | 1 +
src/app/tab/tabs/tabs.component.spec.ts | 44 +++++++++++++++++--------
3 files changed, 48 insertions(+), 13 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index facbebc1..1f58bc51 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,6 +47,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"jasmine-core": "~5.1.0",
+ "ng-mocks": "^14.13.0",
"postcss": "^8.4.35",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
@@ -11673,6 +11674,21 @@
"rxjs": "^6.5.5 || ^7.4.0"
}
},
+ "node_modules/ng-mocks": {
+ "version": "14.13.0",
+ "resolved": "https://registry.npmjs.org/ng-mocks/-/ng-mocks-14.13.0.tgz",
+ "integrity": "sha512-cQ6nUj/P+v7X52gYU6bAj/03iDKl2pzbPy2V0tx/d5lxME063Vxc190p6UPlJkbRIxcB+OqSALPgQvy83efzjw==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/help-me-mom"
+ },
+ "peerDependencies": {
+ "@angular/common": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18",
+ "@angular/core": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18",
+ "@angular/forms": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18",
+ "@angular/platform-browser": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18"
+ }
+ },
"node_modules/nice-napi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
diff --git a/package.json b/package.json
index ad8dce67..6ace0ace 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"jasmine-core": "~5.1.0",
+ "ng-mocks": "^14.13.0",
"postcss": "^8.4.35",
"prettier": "^3.2.5",
"prettier-eslint": "^16.3.0",
diff --git a/src/app/tab/tabs/tabs.component.spec.ts b/src/app/tab/tabs/tabs.component.spec.ts
index c2822189..5b97e8fe 100644
--- a/src/app/tab/tabs/tabs.component.spec.ts
+++ b/src/app/tab/tabs/tabs.component.spec.ts
@@ -1,23 +1,41 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
+import { TestBed } from '@angular/core/testing';
+import { MockRender, ngMocks } from 'ng-mocks';
import { TabsComponent } from './tabs.component';
+import { TabItemComponent } from '../tab-item/tab-item.component';
describe('TabsComponent', () => {
- let component: TabsComponent;
- let fixture: ComponentFixture;
-
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [TabsComponent]
- })
- .compileComponents();
-
- fixture = TestBed.createComponent(TabsComponent);
- component = fixture.componentInstance;
+ imports: [TabsComponent, TabItemComponent],
+ }).compileComponents();
+ });
+
+ it('should select 1st tab is no active tab', () => {
+ const fixture = MockRender(`
+
+ Test
+ Test
+ `);
+
+ const component = ngMocks.findInstance(TabsComponent);
fixture.detectChanges();
+
+ expect(component.tabs.first.active()).toBeTrue();
});
- it('should create', () => {
- expect(component).toBeTruthy();
+ it('should select 1st active tab when multiple active tabs', () => {
+ const fixture = MockRender(`
+
+
+
+
+ `);
+
+ const component = ngMocks.findInstance(TabsComponent);
+ fixture.detectChanges();
+
+ const activeTabs = component.tabs.filter(tab => tab.active());
+ expect(activeTabs.length).toBe(1);
+ expect(activeTabs[0].title()).toBe('tab 2');
});
});
From f7a6e4d0092f3a1c3a4160f1a9076cb18c2989a8 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Thu, 18 Jul 2024 14:48:10 +0100
Subject: [PATCH 12/37] Update table styling
---
.../carbon-estimation-table.component.html | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index ec114c9a..f7f57590 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -9,19 +9,19 @@
@for (emissionsItem of emissions; track $index) {
@if (!emissionsItem.parent) {
-
+ |
{{ emissionsItem.category }}
|
{{ emissionsItem.emissions }} |
} @else if (emissionsItem.display) {
-
+ |
From 1068cfeb5065e478e8553e8daab475a4a9b4346f Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Thu, 18 Jul 2024 17:09:19 +0100
Subject: [PATCH 13/37] Fix treemap not rendering properly after resize
---
.../carbon-estimation.component.html | 5 +++--
.../carbon-estimation.component.ts | 13 ++++++++++++-
src/app/tab/tab-item/tab-item.component.ts | 6 ++++--
src/app/tab/tabs/tabs.component.ts | 1 +
4 files changed, 20 insertions(+), 5 deletions(-)
diff --git a/src/app/carbon-estimation/carbon-estimation.component.html b/src/app/carbon-estimation/carbon-estimation.component.html
index dd7ac551..21d74ed6 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.html
+++ b/src/app/carbon-estimation/carbon-estimation.component.html
@@ -22,12 +22,13 @@ Estimations
-
+
-
+
diff --git a/src/app/carbon-estimation/carbon-estimation.component.ts b/src/app/carbon-estimation/carbon-estimation.component.ts
index 10bf764a..2980a3e2 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.ts
+++ b/src/app/carbon-estimation/carbon-estimation.component.ts
@@ -27,11 +27,13 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
public extraHeight = input();
@ViewChild('detailsPanel', { static: true, read: ElementRef }) detailsPanel!: ElementRef;
+ @ViewChild('treemap', { static: true }) treemap!: CarbonEstimationTreemapComponent;
public chartHeight!: number;
private estimatorBaseHeight = sumValues(estimatorHeights);
private resizeSubscription!: Subscription;
+ private hasResized = true;
constructor(private changeDetectorRef: ChangeDetectorRef) {}
@@ -47,7 +49,8 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
this.resizeSubscription.unsubscribe();
}
- onResize(innerHeight: number, innerWidth: number, screenHeight: number): void {
+ public onResize(innerHeight: number, innerWidth: number, screenHeight: number): void {
+ this.hasResized = true;
this.chartHeight = this.getChartHeight(innerHeight, innerWidth, screenHeight);
}
@@ -56,6 +59,14 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
this.onResize(window.innerHeight, window.innerWidth, window.screen.height);
}
+ public treemapSelected(): void {
+ if (this.hasResized) {
+ this.hasResized = false;
+ this.changeDetectorRef.detectChanges();
+ this.treemap.chart?.updateOptions({});
+ }
+ }
+
private getChartHeight(innerHeight: number, innerWidth: number, screenHeight: number): number {
const expansionPanelHeight = this.detailsPanel.nativeElement.clientHeight;
diff --git a/src/app/tab/tab-item/tab-item.component.ts b/src/app/tab/tab-item/tab-item.component.ts
index 1f7114fd..81caa968 100644
--- a/src/app/tab/tab-item/tab-item.component.ts
+++ b/src/app/tab/tab-item/tab-item.component.ts
@@ -1,12 +1,14 @@
-import { Component, input, model } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Component, EventEmitter, input, model, Output } from '@angular/core';
@Component({
selector: 'tab-item',
standalone: true,
- imports: [],
+ imports: [CommonModule],
templateUrl: './tab-item.component.html',
})
export class TabItemComponent {
public active = model(false);
public title = input.required();
+ @Output() public tabSelected = new EventEmitter();
}
diff --git a/src/app/tab/tabs/tabs.component.ts b/src/app/tab/tabs/tabs.component.ts
index 1c217dc6..3c2a2075 100644
--- a/src/app/tab/tabs/tabs.component.ts
+++ b/src/app/tab/tabs/tabs.component.ts
@@ -23,5 +23,6 @@ export class TabsComponent implements AfterContentInit {
public selectTab(selectedTab: TabItemComponent) {
this.tabs.filter(tab => tab.active()).forEach(tab => tab.active.set(false));
selectedTab.active.set(true);
+ selectedTab.tabSelected.emit();
}
}
From 78870f82c8f3090857b08678ffe5459dc38a7d92 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 22 Jul 2024 11:27:14 +0100
Subject: [PATCH 14/37] Add table placeholder
---
.../carbon-estimation-table.component.html | 26 ++--
.../carbon-estimation-table.component.ts | 116 +++++++++---------
.../carbon-estimation.constants.ts | 23 ++++
3 files changed, 96 insertions(+), 69 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index f7f57590..08a27bcb 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -6,33 +6,33 @@
|
- @for (emissionsItem of emissions; track $index) {
-
- @if (!emissionsItem.parent) {
+ @for (tableItem of tableData(); track $index) {
+
+ @if (!tableItem.parent) {
- {{ emissionsItem.category }}
+ {{ tableItem.category }}
|
- {{ emissionsItem.emissions }} |
- } @else if (emissionsItem.display) {
+ {{ tableItem.emissions }} |
+ } @else if (tableItem.display) {
-
+ [style.background-color]="tableItem.colour.svg ?? tableItem.colour.background">
+
- {{ emissionsItem.category }}
+ {{ tableItem.category }}
|
- {{ emissionsItem.emissions }}
+ {{ tableItem.emissions }}
|
}
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index 2bb5f999..410c74ce 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -1,11 +1,15 @@
-import { Component, effect, input } from '@angular/core';
+import { Component, computed, effect, input } from '@angular/core';
import { CarbonEstimation } from '../types/carbon-estimator';
-import { EmissionsColours, EmissionsLabels } from '../carbon-estimation/carbon-estimation.constants';
+import {
+ EmissionsColours,
+ EmissionsLabels,
+ placeholderTableData,
+} from '../carbon-estimation/carbon-estimation.constants';
import { CarbonEstimationUtilService } from '../services/carbon-estimation-util.service';
import { NumberObject } from '../utils/number-object';
import { NgClass, NgStyle } from '@angular/common';
-type TableItem = {
+export type TableItem = {
category: string;
emissions: string;
parent?: string;
@@ -26,9 +30,11 @@ type ItemColour = {
templateUrl: './carbon-estimation-table.component.html',
})
export class CarbonEstimationTableComponent {
- public carbonEstimation = input.required();
+ public carbonEstimation = input();
public emissions: TableItem[] = [];
+ public tableData = computed(() => this.getTableData(this.carbonEstimation()));
+
public expanded: { [key: string]: boolean } = {
[EmissionsLabels.Upstream]: true,
[EmissionsLabels.Direct]: true,
@@ -36,11 +42,7 @@ export class CarbonEstimationTableComponent {
[EmissionsLabels.Downstream]: true,
};
- constructor(private carbonEstimationUtilService: CarbonEstimationUtilService) {
- effect(() => {
- this.emissions = this.getEmissions(this.carbonEstimation());
- });
- }
+ constructor(private carbonEstimationUtilService: CarbonEstimationUtilService) {}
public toggle(category: string): void {
this.emissions.forEach(emission => {
@@ -51,53 +53,55 @@ export class CarbonEstimationTableComponent {
this.expanded[category] = !this.expanded[category];
}
- public getEmissions(carbonEstimation: CarbonEstimation): TableItem[] {
- return [
- {
- category: EmissionsLabels.Upstream,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.upstreamEmissions),
- colour: { background: EmissionsColours.Upstream },
- },
- ...this.getEmissionsBreakdown(
- carbonEstimation.upstreamEmissions,
- EmissionsLabels.Upstream,
- EmissionsColours.Upstream,
- EmissionsColours.UpstreamLight
- ),
- {
- category: EmissionsLabels.Direct,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.directEmissions),
- colour: { background: EmissionsColours.Direct },
- },
- ...this.getEmissionsBreakdown(
- carbonEstimation.directEmissions,
- EmissionsLabels.Direct,
- EmissionsColours.Direct,
- EmissionsColours.OperationLight
- ),
- {
- category: EmissionsLabels.Indirect,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.indirectEmissions),
- colour: { background: EmissionsColours.Indirect },
- },
- ...this.getEmissionsBreakdown(
- carbonEstimation.indirectEmissions,
- EmissionsLabels.Indirect,
- EmissionsColours.Indirect,
- EmissionsColours.OperationLight
- ),
- {
- category: EmissionsLabels.Downstream,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.downstreamEmissions),
- colour: { background: EmissionsColours.Downstream },
- },
- ...this.getEmissionsBreakdown(
- carbonEstimation.downstreamEmissions,
- EmissionsLabels.Downstream,
- EmissionsColours.Downstream,
- EmissionsColours.DownstreamLight
- ),
- ];
+ public getTableData(carbonEstimation?: CarbonEstimation): TableItem[] {
+ return !carbonEstimation ? placeholderTableData : (
+ [
+ {
+ category: EmissionsLabels.Upstream,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.upstreamEmissions),
+ colour: { background: EmissionsColours.Upstream },
+ },
+ ...this.getEmissionsBreakdown(
+ carbonEstimation.upstreamEmissions,
+ EmissionsLabels.Upstream,
+ EmissionsColours.Upstream,
+ EmissionsColours.UpstreamLight
+ ),
+ {
+ category: EmissionsLabels.Direct,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.directEmissions),
+ colour: { background: EmissionsColours.Direct },
+ },
+ ...this.getEmissionsBreakdown(
+ carbonEstimation.directEmissions,
+ EmissionsLabels.Direct,
+ EmissionsColours.Direct,
+ EmissionsColours.OperationLight
+ ),
+ {
+ category: EmissionsLabels.Indirect,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.indirectEmissions),
+ colour: { background: EmissionsColours.Indirect },
+ },
+ ...this.getEmissionsBreakdown(
+ carbonEstimation.indirectEmissions,
+ EmissionsLabels.Indirect,
+ EmissionsColours.Indirect,
+ EmissionsColours.OperationLight
+ ),
+ {
+ category: EmissionsLabels.Downstream,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.downstreamEmissions),
+ colour: { background: EmissionsColours.Downstream },
+ },
+ ...this.getEmissionsBreakdown(
+ carbonEstimation.downstreamEmissions,
+ EmissionsLabels.Downstream,
+ EmissionsColours.Downstream,
+ EmissionsColours.DownstreamLight
+ ),
+ ]
+ );
}
private getEmissionsBreakdown(
diff --git a/src/app/carbon-estimation/carbon-estimation.constants.ts b/src/app/carbon-estimation/carbon-estimation.constants.ts
index ce4ac6bc..62f7f9e5 100644
--- a/src/app/carbon-estimation/carbon-estimation.constants.ts
+++ b/src/app/carbon-estimation/carbon-estimation.constants.ts
@@ -1,3 +1,4 @@
+import { TableItem } from '../carbon-estimation-table/carbon-estimation-table.component';
import { ChartOptions } from '../types/carbon-estimator';
export enum EmissionsColours {
@@ -190,3 +191,25 @@ export const placeholderData: ApexChartSeriesItem[] = [
],
},
];
+export const placeholderTableData: TableItem[] = [
+ {
+ category: EmissionsLabels.Upstream,
+ emissions: '?',
+ colour: { background: PlaceholderEmissionsColours.Upstream },
+ },
+ {
+ category: EmissionsLabels.Direct,
+ emissions: '?',
+ colour: { background: PlaceholderEmissionsColours.Direct },
+ },
+ {
+ category: EmissionsLabels.Indirect,
+ emissions: '?',
+ colour: { background: PlaceholderEmissionsColours.Indirect },
+ },
+ {
+ category: EmissionsLabels.Downstream,
+ emissions: '?',
+ colour: { background: PlaceholderEmissionsColours.Downstream },
+ },
+];
From ab80298bb5c450e3ab8cfd896e5e2bfea5c0134a Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 23 Jul 2024 09:04:38 +0100
Subject: [PATCH 15/37] Update tab names
---
src/app/carbon-estimation/carbon-estimation.component.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/app/carbon-estimation/carbon-estimation.component.html b/src/app/carbon-estimation/carbon-estimation.component.html
index 21d74ed6..51703239 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.html
+++ b/src/app/carbon-estimation/carbon-estimation.component.html
@@ -22,13 +22,13 @@ Estimations
-
+
-
+
From 02080973882800066bad982313c3cfb8f01e8988 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 23 Jul 2024 15:58:41 +0100
Subject: [PATCH 16/37] Update tab names and make diagram main active tab
---
src/app/carbon-estimation/carbon-estimation.component.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/app/carbon-estimation/carbon-estimation.component.html b/src/app/carbon-estimation/carbon-estimation.component.html
index 51703239..673b9b15 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.html
+++ b/src/app/carbon-estimation/carbon-estimation.component.html
@@ -22,13 +22,13 @@ Estimations
-
+
-
+
From 4de2967740d8bc04f1e6c3cf73db377c29e16898 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 23 Jul 2024 15:59:27 +0100
Subject: [PATCH 17/37] Fix unit tests
---
.../carbon-estimation-table.component.spec.ts | 8 ++++----
.../carbon-estimation-treemap.component.spec.ts | 10 +++++-----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
index e88bc210..fa15f6d9 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
@@ -53,8 +53,8 @@ describe('CarbonEstimationTableComponent', () => {
it('should toggle display value of child emissions when toggle called', () => {
component.toggle('Upstream');
- expect(component.emissions[0].display).toBeFalse();
- component.emissions.forEach(emission => {
+ expect(component.tableData()[0].display).toBeFalse();
+ component.tableData().forEach(emission => {
if (emission.parent === 'Upstream') {
expect(emission.display).toBeFalse();
}
@@ -62,7 +62,7 @@ describe('CarbonEstimationTableComponent', () => {
});
it('should set child emissions to display by default', () => {
- component.emissions.forEach(emission => {
+ component.tableData().forEach(emission => {
if (emission.parent) {
expect(emission.display).toBeFalse();
}
@@ -70,7 +70,7 @@ describe('CarbonEstimationTableComponent', () => {
});
it('should get emissions when getEmissions called', () => {
- const emissions = component.getEmissions(component.carbonEstimation());
+ const emissions = component.getTableData(component.carbonEstimation());
expect(emissions.length).toBe(13);
});
});
diff --git a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
index c9feae59..7448dab4 100644
--- a/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
+++ b/src/app/carbon-estimation-treemap/carbon-estimation-treemap.component.spec.ts
@@ -133,11 +133,11 @@ describe('CarbonEstimationTreemapComponent', () => {
},
];
- expect(component.emissions).toEqual(expectedEmissions);
+ expect(component.chartData()).toEqual(expectedEmissions);
});
it('should have detailed aria label', () => {
- expect(component.emissionAriaLabel.length).toBeGreaterThan(25);
+ expect(component.emissionAriaLabel().length).toBeGreaterThan(25);
});
it('should set label to <1% if emission is less than 1', () => {
@@ -168,7 +168,7 @@ describe('CarbonEstimationTreemapComponent', () => {
fixture.detectChanges();
- expect(component.emissions[0].name).toBe('Upstream Emissions - <1%');
+ expect(component.chartData()[0].name).toBe('Upstream Emissions - <1%');
});
it('should remove categories when they are 0', () => {
@@ -246,7 +246,7 @@ describe('CarbonEstimationTreemapComponent', () => {
},
];
- expect(component.emissions).toEqual(expectedEmissions);
+ expect(component.chartData()).toEqual(expectedEmissions);
});
it('should remove parent categories when all values are 0', () => {
@@ -302,6 +302,6 @@ describe('CarbonEstimationTreemapComponent', () => {
},
];
- expect(component.emissions).toEqual(expectedEmissions);
+ expect(component.chartData()).toEqual(expectedEmissions);
});
});
From 364e7791b254d6a35d5952587c28fa49472ac315 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 23 Jul 2024 16:03:22 +0100
Subject: [PATCH 18/37] Fix row toggle, and make expanded state maintained
---
.../carbon-estimation-table.component.ts | 24 +++++++++++--------
1 file changed, 14 insertions(+), 10 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index 410c74ce..877b6f7a 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -31,7 +31,6 @@ type ItemColour = {
})
export class CarbonEstimationTableComponent {
public carbonEstimation = input();
- public emissions: TableItem[] = [];
public tableData = computed(() => this.getTableData(this.carbonEstimation()));
@@ -45,7 +44,7 @@ export class CarbonEstimationTableComponent {
constructor(private carbonEstimationUtilService: CarbonEstimationUtilService) {}
public toggle(category: string): void {
- this.emissions.forEach(emission => {
+ this.tableData().forEach(emission => {
if (emission.parent === category) {
emission.display = !emission.display;
}
@@ -65,7 +64,8 @@ export class CarbonEstimationTableComponent {
carbonEstimation.upstreamEmissions,
EmissionsLabels.Upstream,
EmissionsColours.Upstream,
- EmissionsColours.UpstreamLight
+ EmissionsColours.UpstreamLight,
+ this.expanded[EmissionsLabels.Upstream]
),
{
category: EmissionsLabels.Direct,
@@ -76,7 +76,8 @@ export class CarbonEstimationTableComponent {
carbonEstimation.directEmissions,
EmissionsLabels.Direct,
EmissionsColours.Direct,
- EmissionsColours.OperationLight
+ EmissionsColours.OperationLight,
+ this.expanded[EmissionsLabels.Direct]
),
{
category: EmissionsLabels.Indirect,
@@ -87,7 +88,8 @@ export class CarbonEstimationTableComponent {
carbonEstimation.indirectEmissions,
EmissionsLabels.Indirect,
EmissionsColours.Indirect,
- EmissionsColours.OperationLight
+ EmissionsColours.OperationLight,
+ this.expanded[EmissionsLabels.Indirect]
),
{
category: EmissionsLabels.Downstream,
@@ -98,7 +100,8 @@ export class CarbonEstimationTableComponent {
carbonEstimation.downstreamEmissions,
EmissionsLabels.Downstream,
EmissionsColours.Downstream,
- EmissionsColours.DownstreamLight
+ EmissionsColours.DownstreamLight,
+ this.expanded[EmissionsLabels.Downstream]
),
]
);
@@ -108,19 +111,20 @@ export class CarbonEstimationTableComponent {
emissions: NumberObject,
parent: string,
svgColour: string,
- backgroundColour: string
+ backgroundColour: string,
+ display = true
): TableItem[] {
return (
Object.entries(emissions)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.filter(([_key, value]) => value !== 0)
.map(([_key, value]) =>
- this.getTableItem(_key, value, parent, { background: backgroundColour, svg: svgColour })
+ this.getTableItem(_key, value, parent, { background: backgroundColour, svg: svgColour }, display)
)
);
}
- private getTableItem(key: string, value: number, parent: string, colour: ItemColour): TableItem {
+ private getTableItem(key: string, value: number, parent: string, colour: ItemColour, display: boolean): TableItem {
const { label, svg } = this.carbonEstimationUtilService.getLabelAndSvg(key, parent);
return {
category: label,
@@ -128,7 +132,7 @@ export class CarbonEstimationTableComponent {
parent,
svg,
colour,
- display: true,
+ display,
};
}
}
From 146341d20ff2203c06112d27aab2346216c6e270 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 23 Jul 2024 16:04:31 +0100
Subject: [PATCH 19/37] Make sure treemap re-render if values change
---
.../carbon-estimation.component.ts | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/src/app/carbon-estimation/carbon-estimation.component.ts b/src/app/carbon-estimation/carbon-estimation.component.ts
index 2980a3e2..0eeafaf7 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.ts
+++ b/src/app/carbon-estimation/carbon-estimation.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Component, ElementRef, input, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { ChangeDetectorRef, Component, computed, ElementRef, input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ExpansionPanelComponent } from '../expansion-panel/expansion-panel.component';
import { TabsComponent } from '../tab/tabs/tabs.component';
import { TabItemComponent } from '../tab/tab-item/tab-item.component';
@@ -6,8 +6,9 @@ import { CarbonEstimationTreemapComponent } from '../carbon-estimation-treemap/c
import { CarbonEstimation } from '../types/carbon-estimator';
import { sumValues } from '../utils/number-object';
import { estimatorHeights } from './carbon-estimation.constants';
-import { debounceTime, fromEvent, Subscription } from 'rxjs';
+import { debounceTime, fromEvent, Observable, Subscription } from 'rxjs';
import { CarbonEstimationTableComponent } from '../carbon-estimation-table/carbon-estimation-table.component';
+import { toObservable } from '@angular/core/rxjs-interop';
@Component({
selector: 'carbon-estimation',
@@ -34,8 +35,14 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
private estimatorBaseHeight = sumValues(estimatorHeights);
private resizeSubscription!: Subscription;
private hasResized = true;
+ private hasEstimationUpdated = false;
+ private carbonEstimationSubscription?: Subscription;
- constructor(private changeDetectorRef: ChangeDetectorRef) {}
+ constructor(private changeDetectorRef: ChangeDetectorRef) {
+ this.carbonEstimationSubscription = toObservable(this.carbonEstimation).subscribe(() => {
+ this.hasEstimationUpdated = true;
+ });
+ }
public ngOnInit(): void {
this.chartHeight = this.getChartHeight(window.innerHeight, window.innerWidth, window.screen.height);
@@ -47,6 +54,7 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
public ngOnDestroy(): void {
this.resizeSubscription.unsubscribe();
+ this.carbonEstimationSubscription?.unsubscribe();
}
public onResize(innerHeight: number, innerWidth: number, screenHeight: number): void {
@@ -60,8 +68,9 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
}
public treemapSelected(): void {
- if (this.hasResized) {
+ if (this.hasResized || this.hasEstimationUpdated) {
this.hasResized = false;
+ this.hasEstimationUpdated = false;
this.changeDetectorRef.detectChanges();
this.treemap.chart?.updateOptions({});
}
From 2d233b52ebb68a978706cb1e7d5431435690f17b Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Wed, 24 Jul 2024 16:24:40 +0100
Subject: [PATCH 20/37] Make the table accessible
---
.../carbon-estimation-table.component.html | 73 +++--
.../carbon-estimation-table.component.ts | 271 +++++++++++++++---
.../carbon-estimation.constants.ts | 16 ++
3 files changed, 296 insertions(+), 64 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index 08a27bcb..8a9a8574 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -1,41 +1,72 @@
-
+
+
Category |
Emissions |
-
+
@for (tableItem of tableData(); track $index) {
-
- @if (!tableItem.parent) {
-
- |
+ } @else {
+
+
-
- {{ tableItem.category }}
-
+ {{ tableItem.category }}
|
-
+ |
{{ tableItem.emissions }}
|
- }
-
+
+ }
}
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index 877b6f7a..b4216ea9 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -1,4 +1,4 @@
-import { Component, computed, effect, input } from '@angular/core';
+import { ChangeDetectorRef, Component, computed, input } from '@angular/core';
import { CarbonEstimation } from '../types/carbon-estimator';
import {
EmissionsColours,
@@ -12,10 +12,14 @@ import { NgClass, NgStyle } from '@angular/common';
export type TableItem = {
category: string;
emissions: string;
+ colour: ItemColour;
+ display: boolean;
+ positionInSet: number;
+ setSize: number;
parent?: string;
svg?: string;
- colour: ItemColour;
- display?: boolean;
+ expanded?: boolean;
+ level: number;
};
type ItemColour = {
@@ -23,6 +27,11 @@ type ItemColour = {
background: string;
};
+type ArrowDirection = ArrowDirectionHorizontal | ArrowDirectionVertical;
+
+type ArrowDirectionHorizontal = 'left' | 'right';
+type ArrowDirectionVertical = 'up' | 'down';
+
@Component({
selector: 'carbon-estimation-table',
standalone: true,
@@ -34,74 +43,211 @@ export class CarbonEstimationTableComponent {
public tableData = computed(() => this.getTableData(this.carbonEstimation()));
- public expanded: { [key: string]: boolean } = {
- [EmissionsLabels.Upstream]: true,
- [EmissionsLabels.Direct]: true,
- [EmissionsLabels.Indirect]: true,
- [EmissionsLabels.Downstream]: true,
- };
-
- constructor(private carbonEstimationUtilService: CarbonEstimationUtilService) {}
+ constructor(
+ private carbonEstimationUtilService: CarbonEstimationUtilService,
+ private changeDetector: ChangeDetectorRef
+ ) {}
public toggle(category: string): void {
this.tableData().forEach(emission => {
if (emission.parent === category) {
emission.display = !emission.display;
+ } else if (emission.category === category) {
+ emission.expanded = !emission.expanded;
}
});
- this.expanded[category] = !this.expanded[category];
+ }
+
+ public parentRowArrowKeyBoardEvent(
+ keyBoardEvent: Event,
+ direction: ArrowDirectionHorizontal,
+ parent: string,
+ expanded?: boolean
+ ): void {
+ if (
+ keyBoardEvent.target === keyBoardEvent.currentTarget &&
+ ((direction === 'left' && expanded) || (direction === 'right' && !expanded))
+ ) {
+ keyBoardEvent.preventDefault();
+ this.toggle(parent);
+ } else {
+ this.arrowKeyBoardEvent(keyBoardEvent, direction);
+ }
+ }
+
+ public arrowKeyBoardEvent(keyBoardEvent: Event, direction: ArrowDirection): void {
+ keyBoardEvent.preventDefault();
+ const tagName = (keyBoardEvent.target as HTMLElement).tagName;
+
+ if (tagName === 'TR') {
+ this.moveRowFocus(keyBoardEvent, direction);
+ } else if (tagName === 'TD') {
+ this.moveCellFocus(keyBoardEvent, direction);
+ }
+ }
+
+ public homeEndKeyBoardEvent(event: Event, home: boolean): void {
+ const keyBoardEvent = event as KeyboardEvent;
+ keyBoardEvent.preventDefault();
+ const tagName = (keyBoardEvent.target as HTMLElement).tagName;
+
+ if (tagName === 'TR') {
+ // Row is focused
+ const target = keyBoardEvent.target as HTMLTableRowElement;
+ const table = target.parentElement as HTMLTableElement;
+ const newRow = home ? table.rows[0] : this.getLastVisibleRow(table);
+ this.setNewTabIndexAndFocus(newRow, target);
+ } else if (tagName === 'TD') {
+ // Cell is focused
+ const target = keyBoardEvent.target as HTMLTableCellElement;
+ const row = target.parentElement as HTMLTableRowElement;
+
+ if (keyBoardEvent.ctrlKey) {
+ const table = row.parentElement as HTMLTableElement;
+ const newRow = home ? table.rows[0] : this.getLastVisibleRow(table);
+ const newCell = newRow.cells[target.cellIndex];
+ this.setNewTabIndexAndFocus(newRow, row, false);
+ this.setNewTabIndexAndFocus(newCell, target);
+ } else {
+ const newCell = home ? row.cells[0] : row.cells[row.cells.length - 1];
+ this.setNewTabIndexAndFocus(newCell, target);
+ }
+ }
+ }
+
+ private moveRowFocus(keyBoardEvent: Event, direction: ArrowDirection): void {
+ if (direction === 'right') {
+ this.moveFocusToCell(keyBoardEvent);
+ } else if (direction === 'down' || direction === 'up') {
+ this.moveRowFocusVertically(keyBoardEvent, direction);
+ }
+ }
+
+ private moveFocusToCell(keyBoardEvent: Event): void {
+ const target = keyBoardEvent.target as HTMLElement;
+ const cell = target.firstElementChild as HTMLElement;
+ cell.tabIndex = 0;
+ this.changeDetector.detectChanges();
+ cell.focus();
+ }
+
+ private moveRowFocusVertically(keyBoardEvent: Event, direction: ArrowDirectionVertical): void {
+ const target = keyBoardEvent.target as HTMLElement;
+ const newRow = this.findNextVisibleRow(target as HTMLTableRowElement, direction);
+ if (newRow) {
+ this.setNewTabIndexAndFocus(newRow, target);
+ }
+ }
+
+ private moveCellFocus(keyBoardEvent: Event, direction: ArrowDirection): void {
+ const target = keyBoardEvent.target as HTMLTableCellElement;
+ const currentRow = target.parentElement as HTMLTableRowElement;
+ let newCell: HTMLTableCellElement | undefined = undefined;
+ let newRow: HTMLTableRowElement | undefined = undefined;
+
+ if (direction === 'left') {
+ newCell = target.previousElementSibling as HTMLTableCellElement;
+ } else if (direction === 'right') {
+ newCell = target.nextElementSibling as HTMLTableCellElement;
+ } else if (direction === 'up' || direction === 'down') {
+ newRow = this.findNextVisibleRow(currentRow, direction);
+ newCell = newRow?.cells[target.cellIndex] as HTMLTableCellElement;
+ }
+
+ if (newCell) {
+ if (newRow) {
+ this.setNewTabIndexAndFocus(newRow, currentRow, false);
+ }
+ this.setNewTabIndexAndFocus(newCell, target);
+ } else if (direction === 'left') {
+ this.moveFocusToRow(keyBoardEvent);
+ }
+ }
+
+ private moveFocusToRow(keyBoardEvent: Event): void {
+ const target = keyBoardEvent.target as HTMLElement;
+ const row = target.parentElement as HTMLElement;
+ target.tabIndex = -1;
+ this.changeDetector.detectChanges();
+ row.focus();
+ }
+
+ private findNextVisibleRow(
+ row: HTMLTableRowElement,
+ direction: ArrowDirectionVertical
+ ): HTMLTableRowElement | undefined {
+ const newRow = (direction === 'up' ? row.previousElementSibling : row.nextElementSibling) as HTMLTableRowElement;
+ return newRow?.classList.contains('tce-hidden') ? this.findNextVisibleRow(newRow, direction) : newRow;
+ }
+
+ private getLastVisibleRow(table: HTMLTableElement): HTMLTableRowElement {
+ const rows = Array.from(table.rows);
+ const lastVisibleRow = rows.reverse().find(row => !row.classList.contains('tce-hidden'));
+ return lastVisibleRow as HTMLTableRowElement;
+ }
+
+ private setNewTabIndexAndFocus(newElement: HTMLElement, oldElement: HTMLElement, setFocus = true): void {
+ oldElement.tabIndex = -1;
+ newElement.tabIndex = 0;
+ if (setFocus) {
+ newElement.focus();
+ }
}
public getTableData(carbonEstimation?: CarbonEstimation): TableItem[] {
return !carbonEstimation ? placeholderTableData : (
[
- {
- category: EmissionsLabels.Upstream,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.upstreamEmissions),
- colour: { background: EmissionsColours.Upstream },
- },
+ this.getParentTableItem(
+ EmissionsLabels.Upstream,
+ carbonEstimation.upstreamEmissions,
+ EmissionsColours.Upstream,
+ 1,
+ 4
+ ),
...this.getEmissionsBreakdown(
carbonEstimation.upstreamEmissions,
EmissionsLabels.Upstream,
EmissionsColours.Upstream,
- EmissionsColours.UpstreamLight,
- this.expanded[EmissionsLabels.Upstream]
+ EmissionsColours.UpstreamLight
+ ),
+ this.getParentTableItem(
+ EmissionsLabels.Direct,
+ carbonEstimation.directEmissions,
+ EmissionsColours.Direct,
+ 2,
+ 4
),
- {
- category: EmissionsLabels.Direct,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.directEmissions),
- colour: { background: EmissionsColours.Direct },
- },
...this.getEmissionsBreakdown(
carbonEstimation.directEmissions,
EmissionsLabels.Direct,
EmissionsColours.Direct,
- EmissionsColours.OperationLight,
- this.expanded[EmissionsLabels.Direct]
+ EmissionsColours.OperationLight
+ ),
+ this.getParentTableItem(
+ EmissionsLabels.Indirect,
+ carbonEstimation.indirectEmissions,
+ EmissionsColours.Indirect,
+ 3,
+ 4
),
- {
- category: EmissionsLabels.Indirect,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.indirectEmissions),
- colour: { background: EmissionsColours.Indirect },
- },
...this.getEmissionsBreakdown(
carbonEstimation.indirectEmissions,
EmissionsLabels.Indirect,
EmissionsColours.Indirect,
- EmissionsColours.OperationLight,
- this.expanded[EmissionsLabels.Indirect]
+ EmissionsColours.OperationLight
+ ),
+ this.getParentTableItem(
+ EmissionsLabels.Downstream,
+ carbonEstimation.downstreamEmissions,
+ EmissionsColours.Downstream,
+ 4,
+ 4
),
- {
- category: EmissionsLabels.Downstream,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(carbonEstimation.downstreamEmissions),
- colour: { background: EmissionsColours.Downstream },
- },
...this.getEmissionsBreakdown(
carbonEstimation.downstreamEmissions,
EmissionsLabels.Downstream,
EmissionsColours.Downstream,
- EmissionsColours.DownstreamLight,
- this.expanded[EmissionsLabels.Downstream]
+ EmissionsColours.DownstreamLight
),
]
);
@@ -118,13 +264,49 @@ export class CarbonEstimationTableComponent {
Object.entries(emissions)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.filter(([_key, value]) => value !== 0)
- .map(([_key, value]) =>
- this.getTableItem(_key, value, parent, { background: backgroundColour, svg: svgColour }, display)
+ .map(([key, value], index, array) =>
+ this.getChildTableItem(
+ key,
+ value,
+ parent,
+ { background: backgroundColour, svg: svgColour },
+ display,
+ index + 1,
+ array.length
+ )
)
);
}
- private getTableItem(key: string, value: number, parent: string, colour: ItemColour, display: boolean): TableItem {
+ private getParentTableItem(
+ label: string,
+ value: NumberObject,
+ colour: string,
+ positionInSet: number,
+ setSize: number,
+ expanded: boolean = true
+ ): TableItem {
+ return {
+ category: label,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(value),
+ colour: { background: colour },
+ display: true,
+ expanded,
+ positionInSet,
+ setSize,
+ level: 1,
+ };
+ }
+
+ private getChildTableItem(
+ key: string,
+ value: number,
+ parent: string,
+ colour: ItemColour,
+ display: boolean,
+ positionInSet: number,
+ setSize: number
+ ): TableItem {
const { label, svg } = this.carbonEstimationUtilService.getLabelAndSvg(key, parent);
return {
category: label,
@@ -133,6 +315,9 @@ export class CarbonEstimationTableComponent {
svg,
colour,
display,
+ positionInSet,
+ setSize,
+ level: 2,
};
}
}
diff --git a/src/app/carbon-estimation/carbon-estimation.constants.ts b/src/app/carbon-estimation/carbon-estimation.constants.ts
index 62f7f9e5..498ba5a3 100644
--- a/src/app/carbon-estimation/carbon-estimation.constants.ts
+++ b/src/app/carbon-estimation/carbon-estimation.constants.ts
@@ -196,20 +196,36 @@ export const placeholderTableData: TableItem[] = [
category: EmissionsLabels.Upstream,
emissions: '?',
colour: { background: PlaceholderEmissionsColours.Upstream },
+ display: true,
+ positionInSet: 1,
+ setSize: 4,
+ level: 1,
},
{
category: EmissionsLabels.Direct,
emissions: '?',
colour: { background: PlaceholderEmissionsColours.Direct },
+ display: true,
+ positionInSet: 1,
+ setSize: 4,
+ level: 1,
},
{
category: EmissionsLabels.Indirect,
emissions: '?',
colour: { background: PlaceholderEmissionsColours.Indirect },
+ display: true,
+ positionInSet: 1,
+ setSize: 4,
+ level: 1,
},
{
category: EmissionsLabels.Downstream,
emissions: '?',
colour: { background: PlaceholderEmissionsColours.Downstream },
+ display: true,
+ positionInSet: 1,
+ setSize: 4,
+ level: 1,
},
];
From fcf7566c56ef25f2abdddaceb8d7a01b1f9a7a5f Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Wed, 24 Jul 2024 16:24:54 +0100
Subject: [PATCH 21/37] Add spellings to workspace
---
.vscode/settings.json | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5bc8d200..4a1b97fa 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,5 +2,12 @@
"typescript.tsdk": "node_modules\\typescript\\lib",
"editor.tabSize": 2,
"files.eol": "\n",
- "cSpell.words": ["apexcharts", "treemap"]
+ "cSpell.words": [
+ "apexcharts",
+ "arrowdown",
+ "arrowleft",
+ "arrowright",
+ "arrowup",
+ "treemap"
+ ]
}
From 415db35dd3eb6c1a3d624f1957308a6862baabc0 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Thu, 25 Jul 2024 09:34:43 +0100
Subject: [PATCH 22/37] Add unit test for keyboard interactions
---
.../carbon-estimation-table.component.spec.ts | 309 +++++++++++++++++-
1 file changed, 295 insertions(+), 14 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
index fa15f6d9..853dbe99 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
@@ -1,24 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { CarbonEstimationTableComponent } from './carbon-estimation-table.component';
+import { CarbonEstimationTableComponent, TableItem } from './carbon-estimation-table.component';
import { CarbonEstimationUtilService } from '../services/carbon-estimation-util.service';
import { CarbonEstimation } from '../types/carbon-estimator';
+import { EmissionsLabels } from '../carbon-estimation/carbon-estimation.constants';
describe('CarbonEstimationTableComponent', () => {
let component: CarbonEstimationTableComponent;
let fixture: ComponentFixture;
- const utilSpy = jasmine.createSpyObj('CarbonEstimationUtilService', [
- 'getOverallPercentageLabel, getPercentageLabel, getLabelAndSvg',
- ]);
-
- utilSpy.getOverallPercentageLabel.and.returnValue('7%');
- utilSpy.getPercentageLabel.and.returnValue('7%');
- utilSpy.getLabelAndSvg.and.returnValue({ label: 'Emissions', svg: 'svg' });
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [CarbonEstimationTableComponent],
- providers: [{ provide: CarbonEstimationUtilService, useValue: utilSpy }],
+ providers: [CarbonEstimationUtilService],
}).compileComponents();
fixture = TestBed.createComponent(CarbonEstimationTableComponent);
@@ -52,10 +46,13 @@ describe('CarbonEstimationTableComponent', () => {
});
it('should toggle display value of child emissions when toggle called', () => {
- component.toggle('Upstream');
- expect(component.tableData()[0].display).toBeFalse();
+ component.toggle(EmissionsLabels.Upstream);
+ fixture.detectChanges();
component.tableData().forEach(emission => {
- if (emission.parent === 'Upstream') {
+ if (emission.category === EmissionsLabels.Upstream) {
+ expect(emission.expanded).toBeFalse();
+ }
+ if (emission.parent === EmissionsLabels.Upstream) {
expect(emission.display).toBeFalse();
}
});
@@ -64,13 +61,297 @@ describe('CarbonEstimationTableComponent', () => {
it('should set child emissions to display by default', () => {
component.tableData().forEach(emission => {
if (emission.parent) {
- expect(emission.display).toBeFalse();
+ expect(emission.display).toBeTrue();
}
});
});
it('should get emissions when getEmissions called', () => {
const emissions = component.getTableData(component.carbonEstimation());
- expect(emissions.length).toBe(13);
+ expect(emissions.length).toBe(16);
+ });
+
+ it('should call toggle when left arrow clicked on expanded parent row', () => {
+ const toggleSpy = spyOn(component, 'toggle');
+ const parentRow = fixture.debugElement.nativeElement.querySelector('tbody tr');
+
+ parentRow.focus();
+ fixture.detectChanges();
+
+ component.parentRowArrowKeyBoardEvent(
+ { target: parentRow, currentTarget: parentRow, preventDefault: () => {} } as unknown as Event,
+ 'left',
+ EmissionsLabels.Upstream,
+ true
+ );
+
+ expect(toggleSpy).toHaveBeenCalled();
+ });
+
+ it('should call toggle when right arrow clicked on none expanded parent row', () => {
+ const toggleSpy = spyOn(component, 'toggle');
+ const parentRow = fixture.debugElement.nativeElement.querySelector('tbody tr');
+
+ parentRow.focus();
+ fixture.detectChanges();
+ component.parentRowArrowKeyBoardEvent(
+ { target: parentRow, currentTarget: parentRow, preventDefault: () => {} } as unknown as Event,
+ 'right',
+ EmissionsLabels.Upstream,
+ false
+ );
+
+ fixture.detectChanges();
+ expect(toggleSpy).toHaveBeenCalled();
+ });
+
+ it('should move focus to down row when down arrow clicked on row', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.querySelector('tr');
+ row.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: row, preventDefault: () => {} } as unknown as Event, 'down');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[1]);
+ });
+
+ it('should move focus to up row when up arrow clicked on row', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[2];
+ row.tabIndex = 0;
+ fixture.detectChanges();
+ row.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: row, preventDefault: () => {} } as unknown as Event, 'up');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[1]);
+ });
+
+ it('should move focus to last row when end clicked on row', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ row.focus();
+ fixture.detectChanges();
+
+ component.homeEndKeyBoardEvent({ target: row, preventDefault: () => {} } as unknown as Event, false);
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[table.rows.length - 1]);
+ });
+
+ it('should move focus to first row when home clicked on row', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[2];
+ row.tabIndex = 0;
+ fixture.detectChanges();
+ row.focus();
+ fixture.detectChanges();
+
+ component.homeEndKeyBoardEvent({ target: row, preventDefault: () => {} } as unknown as Event, true);
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[0]);
+ });
+
+ it('should move focus to cell when right arrow clicked on row', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.querySelector('tr');
+ row.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: row, preventDefault: () => {} } as unknown as Event, 'right');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(row.cells[0]);
+ });
+
+ it('should move focus to row when left arrow clicked on left hand cell', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ const cell = row.cells[0];
+ cell.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, 'left');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[0]);
+ });
+
+ it('should move focus to left cell when left arrow clicked on right hand cell', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ const cell = row.cells[1];
+ cell.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, 'left');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(row.cells[0]);
+ });
+
+ it('should move focus to right cell when right arrow clicked on left hand cell', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ const cell = row.cells[0];
+ cell.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, 'right');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(row.cells[1]);
+ });
+
+ it('should move focus to down a cell when down arrow clicked on cell', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ const cell = row.cells[0];
+ cell.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, 'down');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[row.sectionRowIndex + 1].cells[cell.cellIndex]);
+ });
+
+ it('should move focus to up a cell when up arrow clicked on cell', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[2];
+ const cell = row.cells[1];
+ cell.tabIndex = 0;
+ row.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, 'up');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[row.sectionRowIndex - 1].cells[1]);
+ });
+
+ it('should move focus to last cell when end clicked on cell', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ const cell = row.cells[0];
+ cell.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.homeEndKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, false);
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(row.cells[row.cells.length - 1]);
+ });
+
+ it('should move focus to first cell when home clicked on cell', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ const cell = row.cells[1];
+ cell.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.homeEndKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, true);
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(row.cells[0]);
+ });
+
+ it('should move focus to corresponding cell in first row when control and home are clicked', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[4];
+ const cell = row.cells[1];
+ cell.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.homeEndKeyBoardEvent({ target: cell, preventDefault: () => {}, ctrlKey: true } as unknown as Event, true);
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[0].cells[cell.cellIndex]);
+ });
+
+ it('should move focus to corresponding cell in last row when control and end are clicked', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[2];
+ const cell = row.cells[0];
+ cell.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.homeEndKeyBoardEvent(
+ { target: cell, preventDefault: () => {}, ctrlKey: true } as unknown as Event,
+ false
+ );
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(table.rows[table.rows.length - 1].cells[cell.cellIndex]);
+ });
+
+ it('should move row to next visible row when up/down arrow clicked', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ component.toggle(EmissionsLabels.Upstream);
+ fixture.detectChanges();
+ row.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: row, preventDefault: () => {} } as unknown as Event, 'down');
+ fixture.detectChanges();
+ expect(document.activeElement).not.toBe(table.rows[1]);
+ expect(document.activeElement).toBe(
+ table.rows[component.tableData().findIndex((emission: TableItem) => emission.category === EmissionsLabels.Direct)]
+ );
+ });
+
+ it('should not move focus when focus is on right edge and press right arrow', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[1];
+ const cell = row.cells[1];
+ cell.tabIndex = 0;
+ row.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, 'right');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(cell);
+ });
+
+ it('should not move focus when focus is on top edge and press up arrow', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[0];
+ const cell = row.cells[1];
+ cell.tabIndex = 0;
+ row.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, 'up');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(cell);
+ });
+
+ it('should not move focus when focus is on bottom edge and press down arrow', () => {
+ const table = fixture.debugElement.nativeElement.querySelector('tbody');
+ const row = table.rows[table.rows.length - 1];
+ const cell = row.cells[1];
+ cell.tabIndex = 0;
+ row.tabIndex = 0;
+ fixture.detectChanges();
+ cell.focus();
+ fixture.detectChanges();
+
+ component.arrowKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, 'down');
+ fixture.detectChanges();
+ expect(document.activeElement).toBe(cell);
});
});
From cb005d535a85b83955fe8cfe9ed56f29e3023494 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Thu, 25 Jul 2024 15:20:27 +0100
Subject: [PATCH 23/37] Align table headings to be same as columns
---
.../carbon-estimation-table.component.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index 8a9a8574..881be1fc 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -2,8 +2,8 @@
- Category |
- Emissions |
+ Category |
+ Emissions |
Date: Mon, 29 Jul 2024 09:40:12 +0100
Subject: [PATCH 24/37] Hide expand element from screen readers are cell
handles expanding for keyboard users
---
.../carbon-estimation-table.component.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index 881be1fc..de23779b 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -33,6 +33,7 @@
{{ tableItem.expanded ? 'expand_less' : 'expand_more' }}
From 7b634502ffbbd2a59ca83ba328743cf03925208b Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 09:51:39 +0100
Subject: [PATCH 25/37] Tidy up code and add script for package start
---
package.json | 1 +
src/app/carbon-estimation/carbon-estimation.component.ts | 4 ++--
src/app/tab/tabs/tabs.component.spec.ts | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/package.json b/package.json
index 6ace0ace..460f16c3 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"scripts": {
"ng": "ng",
"start": "ng serve",
+ "start:npm-package": "ng serve --configuration npm-package",
"prepare": "ng build --configuration npm-package",
"build": "ng build",
"watch": "ng build --watch --configuration development",
diff --git a/src/app/carbon-estimation/carbon-estimation.component.ts b/src/app/carbon-estimation/carbon-estimation.component.ts
index 0eeafaf7..2c770a7c 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.ts
+++ b/src/app/carbon-estimation/carbon-estimation.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Component, computed, ElementRef, input, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { ChangeDetectorRef, Component, ElementRef, input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ExpansionPanelComponent } from '../expansion-panel/expansion-panel.component';
import { TabsComponent } from '../tab/tabs/tabs.component';
import { TabItemComponent } from '../tab/tab-item/tab-item.component';
@@ -6,7 +6,7 @@ import { CarbonEstimationTreemapComponent } from '../carbon-estimation-treemap/c
import { CarbonEstimation } from '../types/carbon-estimator';
import { sumValues } from '../utils/number-object';
import { estimatorHeights } from './carbon-estimation.constants';
-import { debounceTime, fromEvent, Observable, Subscription } from 'rxjs';
+import { debounceTime, fromEvent, Subscription } from 'rxjs';
import { CarbonEstimationTableComponent } from '../carbon-estimation-table/carbon-estimation-table.component';
import { toObservable } from '@angular/core/rxjs-interop';
diff --git a/src/app/tab/tabs/tabs.component.spec.ts b/src/app/tab/tabs/tabs.component.spec.ts
index 5b97e8fe..43ccc14d 100644
--- a/src/app/tab/tabs/tabs.component.spec.ts
+++ b/src/app/tab/tabs/tabs.component.spec.ts
@@ -10,7 +10,7 @@ describe('TabsComponent', () => {
}).compileComponents();
});
- it('should select 1st tab is no active tab', () => {
+ it('should select 1st tab if no active tab', () => {
const fixture = MockRender(`
Test
From 87f072c80e3dd1ab1ad3243a8f2913a4967ee043 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 11:05:19 +0100
Subject: [PATCH 26/37] Remove optionality of expanded
---
.../carbon-estimation-table.component.ts | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index b4216ea9..fe897bcd 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -9,17 +9,17 @@ import { CarbonEstimationUtilService } from '../services/carbon-estimation-util.
import { NumberObject } from '../utils/number-object';
import { NgClass, NgStyle } from '@angular/common';
-export type TableItem = {
+export type TableItem = TableItemLevel1 | TableItemLevel2;
+
+type TableItemLevel1 = { level: 1; expanded: boolean } & BaseTableItem;
+type TableItemLevel2 = { level: 2; parent: string; svg: string } & BaseTableItem;
+type BaseTableItem = {
category: string;
emissions: string;
colour: ItemColour;
display: boolean;
positionInSet: number;
setSize: number;
- parent?: string;
- svg?: string;
- expanded?: boolean;
- level: number;
};
type ItemColour = {
@@ -50,9 +50,9 @@ export class CarbonEstimationTableComponent {
public toggle(category: string): void {
this.tableData().forEach(emission => {
- if (emission.parent === category) {
+ if (emission.level === 2 && emission.parent === category) {
emission.display = !emission.display;
- } else if (emission.category === category) {
+ } else if (emission.level === 1 && emission.category === category) {
emission.expanded = !emission.expanded;
}
});
@@ -62,7 +62,7 @@ export class CarbonEstimationTableComponent {
keyBoardEvent: Event,
direction: ArrowDirectionHorizontal,
parent: string,
- expanded?: boolean
+ expanded: boolean
): void {
if (
keyBoardEvent.target === keyBoardEvent.currentTarget &&
From d1fe3a3aaf76d0e17f5994cc7d591e60cda369c2 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 12:02:40 +0100
Subject: [PATCH 27/37] Remove placeholder table and no estimations message
instead
---
.../carbon-estimation-table.component.html | 134 ++++++++++--------
.../carbon-estimation-table.component.ts | 13 +-
.../carbon-estimation.constants.ts | 39 -----
3 files changed, 76 insertions(+), 110 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index de23779b..5fc5f8f2 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -1,73 +1,81 @@
-
+
Category |
Emissions |
-
- @for (tableItem of tableData(); track $index) {
- @if (!tableItem.parent) {
-
-
-
-
+ @for (tableItem of tableData(); track $index) {
+ @if (tableItem.level === 1) {
+
+
- {{ tableItem.expanded ? 'expand_less' : 'expand_more' }}
-
- {{ tableItem.category }}
- |
-
- {{ tableItem.emissions }}
- |
-
- } @else {
-
-
-
- {{ tableItem.category }}
- |
-
- {{ tableItem.emissions }}
- |
-
+ class="tce-flex tce-text-white tce-items-center"
+ (keydown.enter)="toggle(tableItem.category)">
+
+
+ {{ tableItem.expanded ? 'expand_less' : 'expand_more' }}
+
+ {{ tableItem.category }}
+ |
+
+ {{ tableItem.emissions }}
+ |
+
+ } @else {
+
+
+
+ {{ tableItem.category }}
+ |
+
+ {{ tableItem.emissions }}
+ |
+
+ }
}
- }
-
+
+ } @else {
+
+
+ No estimation available |
+
+
+ }
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index fe897bcd..d35bd0d7 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -1,10 +1,6 @@
import { ChangeDetectorRef, Component, computed, input } from '@angular/core';
import { CarbonEstimation } from '../types/carbon-estimator';
-import {
- EmissionsColours,
- EmissionsLabels,
- placeholderTableData,
-} from '../carbon-estimation/carbon-estimation.constants';
+import { EmissionsColours, EmissionsLabels } from '../carbon-estimation/carbon-estimation.constants';
import { CarbonEstimationUtilService } from '../services/carbon-estimation-util.service';
import { NumberObject } from '../utils/number-object';
import { NgClass, NgStyle } from '@angular/common';
@@ -195,9 +191,10 @@ export class CarbonEstimationTableComponent {
}
public getTableData(carbonEstimation?: CarbonEstimation): TableItem[] {
- return !carbonEstimation ? placeholderTableData : (
- [
- this.getParentTableItem(
+ return !carbonEstimation ?
+ []
+ : [
+ ...this.getParentTableItems(
EmissionsLabels.Upstream,
carbonEstimation.upstreamEmissions,
EmissionsColours.Upstream,
diff --git a/src/app/carbon-estimation/carbon-estimation.constants.ts b/src/app/carbon-estimation/carbon-estimation.constants.ts
index 498ba5a3..ce4ac6bc 100644
--- a/src/app/carbon-estimation/carbon-estimation.constants.ts
+++ b/src/app/carbon-estimation/carbon-estimation.constants.ts
@@ -1,4 +1,3 @@
-import { TableItem } from '../carbon-estimation-table/carbon-estimation-table.component';
import { ChartOptions } from '../types/carbon-estimator';
export enum EmissionsColours {
@@ -191,41 +190,3 @@ export const placeholderData: ApexChartSeriesItem[] = [
],
},
];
-export const placeholderTableData: TableItem[] = [
- {
- category: EmissionsLabels.Upstream,
- emissions: '?',
- colour: { background: PlaceholderEmissionsColours.Upstream },
- display: true,
- positionInSet: 1,
- setSize: 4,
- level: 1,
- },
- {
- category: EmissionsLabels.Direct,
- emissions: '?',
- colour: { background: PlaceholderEmissionsColours.Direct },
- display: true,
- positionInSet: 1,
- setSize: 4,
- level: 1,
- },
- {
- category: EmissionsLabels.Indirect,
- emissions: '?',
- colour: { background: PlaceholderEmissionsColours.Indirect },
- display: true,
- positionInSet: 1,
- setSize: 4,
- level: 1,
- },
- {
- category: EmissionsLabels.Downstream,
- emissions: '?',
- colour: { background: PlaceholderEmissionsColours.Downstream },
- display: true,
- positionInSet: 1,
- setSize: 4,
- level: 1,
- },
-];
From 4107078829238123cd51f3051f33df98de9e70bd Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 12:03:45 +0100
Subject: [PATCH 28/37] Add maintained expanded state for parent and linked
children
---
.../carbon-estimation-table.component.ts | 82 ++++++++-----------
1 file changed, 36 insertions(+), 46 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index d35bd0d7..f2dfacf0 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -39,6 +39,8 @@ export class CarbonEstimationTableComponent {
public tableData = computed(() => this.getTableData(this.carbonEstimation()));
+ private expandedState: { [key: string]: boolean } = {};
+
constructor(
private carbonEstimationUtilService: CarbonEstimationUtilService,
private changeDetector: ChangeDetectorRef
@@ -48,6 +50,7 @@ export class CarbonEstimationTableComponent {
this.tableData().forEach(emission => {
if (emission.level === 2 && emission.parent === category) {
emission.display = !emission.display;
+ this.expandedState[category] = emission.display;
} else if (emission.level === 1 && emission.category === category) {
emission.expanded = !emission.expanded;
}
@@ -198,56 +201,39 @@ export class CarbonEstimationTableComponent {
EmissionsLabels.Upstream,
carbonEstimation.upstreamEmissions,
EmissionsColours.Upstream,
+ EmissionsColours.UpstreamLight,
1,
- 4
- ),
- ...this.getEmissionsBreakdown(
- carbonEstimation.upstreamEmissions,
- EmissionsLabels.Upstream,
- EmissionsColours.Upstream,
- EmissionsColours.UpstreamLight
+ 4,
+ this.expandedState[EmissionsLabels.Upstream]
),
- this.getParentTableItem(
+ ...this.getParentTableItems(
EmissionsLabels.Direct,
carbonEstimation.directEmissions,
EmissionsColours.Direct,
+ EmissionsColours.OperationLight,
2,
- 4
- ),
- ...this.getEmissionsBreakdown(
- carbonEstimation.directEmissions,
- EmissionsLabels.Direct,
- EmissionsColours.Direct,
- EmissionsColours.OperationLight
+ 4,
+ this.expandedState[EmissionsLabels.Direct]
),
- this.getParentTableItem(
+ ...this.getParentTableItems(
EmissionsLabels.Indirect,
carbonEstimation.indirectEmissions,
EmissionsColours.Indirect,
+ EmissionsColours.OperationLight,
3,
- 4
- ),
- ...this.getEmissionsBreakdown(
- carbonEstimation.indirectEmissions,
- EmissionsLabels.Indirect,
- EmissionsColours.Indirect,
- EmissionsColours.OperationLight
+ 4,
+ this.expandedState[EmissionsLabels.Indirect]
),
- this.getParentTableItem(
+ ...this.getParentTableItems(
EmissionsLabels.Downstream,
carbonEstimation.downstreamEmissions,
EmissionsColours.Downstream,
+ EmissionsColours.DownstreamLight,
4,
- 4
- ),
- ...this.getEmissionsBreakdown(
- carbonEstimation.downstreamEmissions,
- EmissionsLabels.Downstream,
- EmissionsColours.Downstream,
- EmissionsColours.DownstreamLight
+ 4,
+ this.expandedState[EmissionsLabels.Downstream]
),
- ]
- );
+ ];
}
private getEmissionsBreakdown(
@@ -275,24 +261,28 @@ export class CarbonEstimationTableComponent {
);
}
- private getParentTableItem(
+ private getParentTableItems(
label: string,
value: NumberObject,
- colour: string,
+ parentColour: string,
+ childColour: string,
positionInSet: number,
setSize: number,
expanded: boolean = true
- ): TableItem {
- return {
- category: label,
- emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(value),
- colour: { background: colour },
- display: true,
- expanded,
- positionInSet,
- setSize,
- level: 1,
- };
+ ): TableItem[] {
+ return [
+ {
+ category: label,
+ emissions: this.carbonEstimationUtilService.getOverallPercentageLabel(value),
+ colour: { background: parentColour },
+ display: true,
+ expanded,
+ positionInSet,
+ setSize,
+ level: 1,
+ },
+ ...this.getEmissionsBreakdown(value, label, parentColour, childColour, expanded),
+ ];
}
private getChildTableItem(
From 778aa9a35efd6025274d2eccc13ff6a38a4045be Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 12:16:44 +0100
Subject: [PATCH 29/37] Added dark mode text colour for table
---
.../carbon-estimation-table.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index 5fc5f8f2..3dffecac 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -56,7 +56,7 @@
(keydown.arrowright)="arrowKeyBoardEvent($event, 'right')"
(keydown.arrowleft)="arrowKeyBoardEvent($event, 'left')"
[class.tce-hidden]="!tableItem.display">
-
+ |
From 458945b8ed60e96177e6d3fa549cbad0a16d7f4a Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 12:21:37 +0100
Subject: [PATCH 30/37] Fix table unit test since adding table levels
---
.../carbon-estimation-table.component.spec.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
index 853dbe99..827b34b9 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
@@ -49,10 +49,10 @@ describe('CarbonEstimationTableComponent', () => {
component.toggle(EmissionsLabels.Upstream);
fixture.detectChanges();
component.tableData().forEach(emission => {
- if (emission.category === EmissionsLabels.Upstream) {
+ if (emission.level === 1 && emission.category === EmissionsLabels.Upstream) {
expect(emission.expanded).toBeFalse();
}
- if (emission.parent === EmissionsLabels.Upstream) {
+ if (emission.level === 2 && emission.parent === EmissionsLabels.Upstream) {
expect(emission.display).toBeFalse();
}
});
@@ -60,7 +60,7 @@ describe('CarbonEstimationTableComponent', () => {
it('should set child emissions to display by default', () => {
component.tableData().forEach(emission => {
- if (emission.parent) {
+ if (emission.level === 2 && emission.parent) {
expect(emission.display).toBeTrue();
}
});
From c3251e67bc091797d968ade16202cb0beb561af2 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 16:55:49 +0100
Subject: [PATCH 31/37] Change getPercentageLabel to call tooltipFormatter
---
src/app/services/carbon-estimation-util.service.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/app/services/carbon-estimation-util.service.ts b/src/app/services/carbon-estimation-util.service.ts
index cfb2b06b..17687660 100644
--- a/src/app/services/carbon-estimation-util.service.ts
+++ b/src/app/services/carbon-estimation-util.service.ts
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { NumberObject, sumValues } from '../utils/number-object';
import { startCase } from 'lodash-es';
-import { SVG } from '../carbon-estimation/carbon-estimation.constants';
+import { SVG, tooltipFormatter } from '../carbon-estimation/carbon-estimation.constants';
@Injectable({
providedIn: 'root',
@@ -14,7 +14,7 @@ export class CarbonEstimationUtilService {
return this.getPercentageLabel(percentage);
};
- public getPercentageLabel = (percentage: number): string => (percentage < 1 ? '<1%' : Math.round(percentage) + '%');
+ public getPercentageLabel = tooltipFormatter;
public getLabelAndSvg(key: string, parent: string = ''): { label: string; svg: string } {
switch (key) {
From 99b1aa684b5a420ef0bffe727579ee226e0d51fe Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 16:57:12 +0100
Subject: [PATCH 32/37] Change hasUpdated to be changed from an observable to
within an effect function
---
src/app/carbon-estimation/carbon-estimation.component.ts | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/src/app/carbon-estimation/carbon-estimation.component.ts b/src/app/carbon-estimation/carbon-estimation.component.ts
index 2c770a7c..168f5600 100644
--- a/src/app/carbon-estimation/carbon-estimation.component.ts
+++ b/src/app/carbon-estimation/carbon-estimation.component.ts
@@ -1,4 +1,4 @@
-import { ChangeDetectorRef, Component, ElementRef, input, OnDestroy, OnInit, ViewChild } from '@angular/core';
+import { ChangeDetectorRef, Component, effect, ElementRef, input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ExpansionPanelComponent } from '../expansion-panel/expansion-panel.component';
import { TabsComponent } from '../tab/tabs/tabs.component';
import { TabItemComponent } from '../tab/tab-item/tab-item.component';
@@ -8,7 +8,6 @@ import { sumValues } from '../utils/number-object';
import { estimatorHeights } from './carbon-estimation.constants';
import { debounceTime, fromEvent, Subscription } from 'rxjs';
import { CarbonEstimationTableComponent } from '../carbon-estimation-table/carbon-estimation-table.component';
-import { toObservable } from '@angular/core/rxjs-interop';
@Component({
selector: 'carbon-estimation',
@@ -36,10 +35,10 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
private resizeSubscription!: Subscription;
private hasResized = true;
private hasEstimationUpdated = false;
- private carbonEstimationSubscription?: Subscription;
constructor(private changeDetectorRef: ChangeDetectorRef) {
- this.carbonEstimationSubscription = toObservable(this.carbonEstimation).subscribe(() => {
+ effect(() => {
+ this.carbonEstimation();
this.hasEstimationUpdated = true;
});
}
@@ -54,7 +53,6 @@ export class CarbonEstimationComponent implements OnInit, OnDestroy {
public ngOnDestroy(): void {
this.resizeSubscription.unsubscribe();
- this.carbonEstimationSubscription?.unsubscribe();
}
public onResize(innerHeight: number, innerWidth: number, screenHeight: number): void {
From 8e796ba304065f2e4cb89f9176ec76a111728690 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Mon, 29 Jul 2024 16:59:00 +0100
Subject: [PATCH 33/37] Add effect to tab to handle tab being selected event
emitted, instead of Tabs component emitting value
---
src/app/tab/tab-item/tab-item.component.ts | 10 +++++++++-
src/app/tab/tabs/tabs.component.ts | 1 -
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/app/tab/tab-item/tab-item.component.ts b/src/app/tab/tab-item/tab-item.component.ts
index 81caa968..d40fb03a 100644
--- a/src/app/tab/tab-item/tab-item.component.ts
+++ b/src/app/tab/tab-item/tab-item.component.ts
@@ -1,5 +1,5 @@
import { CommonModule } from '@angular/common';
-import { Component, EventEmitter, input, model, Output } from '@angular/core';
+import { Component, effect, EventEmitter, input, model, Output } from '@angular/core';
@Component({
selector: 'tab-item',
@@ -11,4 +11,12 @@ export class TabItemComponent {
public active = model(false);
public title = input.required();
@Output() public tabSelected = new EventEmitter();
+
+ constructor() {
+ effect(() => {
+ if (this.active()) {
+ this.tabSelected.emit();
+ }
+ });
+ }
}
diff --git a/src/app/tab/tabs/tabs.component.ts b/src/app/tab/tabs/tabs.component.ts
index 3c2a2075..1c217dc6 100644
--- a/src/app/tab/tabs/tabs.component.ts
+++ b/src/app/tab/tabs/tabs.component.ts
@@ -23,6 +23,5 @@ export class TabsComponent implements AfterContentInit {
public selectTab(selectedTab: TabItemComponent) {
this.tabs.filter(tab => tab.active()).forEach(tab => tab.active.set(false));
selectedTab.active.set(true);
- selectedTab.tabSelected.emit();
}
}
From 84fad3f060529a6917e713c9ab3bc632afbca4e8 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 30 Jul 2024 14:40:45 +0100
Subject: [PATCH 34/37] Move setting expand state so only once per toggle
---
.../carbon-estimation-table.component.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index f2dfacf0..06abfa59 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -50,9 +50,9 @@ export class CarbonEstimationTableComponent {
this.tableData().forEach(emission => {
if (emission.level === 2 && emission.parent === category) {
emission.display = !emission.display;
- this.expandedState[category] = emission.display;
} else if (emission.level === 1 && emission.category === category) {
emission.expanded = !emission.expanded;
+ this.expandedState[category] = emission.expanded;
}
});
}
From db665cda708b40e6dcef463419d5514648d8c5f9 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 30 Jul 2024 14:55:25 +0100
Subject: [PATCH 35/37] Update home ENd key function to pull if home or end fom
event
---
.../carbon-estimation-table.component.html | 8 +++---
.../carbon-estimation-table.component.spec.ts | 25 ++++++++++++-------
.../carbon-estimation-table.component.ts | 9 ++++---
3 files changed, 25 insertions(+), 17 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index 3dffecac..b0b0c6a9 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -8,10 +8,10 @@
@if (carbonEstimation()) {
@for (tableItem of tableData(); track $index) {
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
index 827b34b9..6de436be 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
@@ -135,7 +135,7 @@ describe('CarbonEstimationTableComponent', () => {
row.focus();
fixture.detectChanges();
- component.homeEndKeyBoardEvent({ target: row, preventDefault: () => {} } as unknown as Event, false);
+ component.homeEndKeyBoardEvent({ target: row, preventDefault: () => {}, key: 'End' } as unknown as Event);
fixture.detectChanges();
expect(document.activeElement).toBe(table.rows[table.rows.length - 1]);
});
@@ -148,7 +148,7 @@ describe('CarbonEstimationTableComponent', () => {
row.focus();
fixture.detectChanges();
- component.homeEndKeyBoardEvent({ target: row, preventDefault: () => {} } as unknown as Event, true);
+ component.homeEndKeyBoardEvent({ target: row, preventDefault: () => {}, key: 'Home' } as unknown as Event);
fixture.detectChanges();
expect(document.activeElement).toBe(table.rows[0]);
});
@@ -244,7 +244,7 @@ describe('CarbonEstimationTableComponent', () => {
cell.focus();
fixture.detectChanges();
- component.homeEndKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, false);
+ component.homeEndKeyBoardEvent({ target: cell, preventDefault: () => {}, key: 'End' } as unknown as Event);
fixture.detectChanges();
expect(document.activeElement).toBe(row.cells[row.cells.length - 1]);
});
@@ -258,7 +258,7 @@ describe('CarbonEstimationTableComponent', () => {
cell.focus();
fixture.detectChanges();
- component.homeEndKeyBoardEvent({ target: cell, preventDefault: () => {} } as unknown as Event, true);
+ component.homeEndKeyBoardEvent({ target: cell, preventDefault: () => {}, key: 'Home' } as unknown as Event);
fixture.detectChanges();
expect(document.activeElement).toBe(row.cells[0]);
});
@@ -272,7 +272,12 @@ describe('CarbonEstimationTableComponent', () => {
cell.focus();
fixture.detectChanges();
- component.homeEndKeyBoardEvent({ target: cell, preventDefault: () => {}, ctrlKey: true } as unknown as Event, true);
+ component.homeEndKeyBoardEvent({
+ target: cell,
+ preventDefault: () => {},
+ ctrlKey: true,
+ key: 'Home',
+ } as unknown as Event);
fixture.detectChanges();
expect(document.activeElement).toBe(table.rows[0].cells[cell.cellIndex]);
});
@@ -286,10 +291,12 @@ describe('CarbonEstimationTableComponent', () => {
cell.focus();
fixture.detectChanges();
- component.homeEndKeyBoardEvent(
- { target: cell, preventDefault: () => {}, ctrlKey: true } as unknown as Event,
- false
- );
+ component.homeEndKeyBoardEvent({
+ target: cell,
+ preventDefault: () => {},
+ ctrlKey: true,
+ key: 'End',
+ } as unknown as Event);
fixture.detectChanges();
expect(document.activeElement).toBe(table.rows[table.rows.length - 1].cells[cell.cellIndex]);
});
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index 06abfa59..594f9be1 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -85,8 +85,9 @@ export class CarbonEstimationTableComponent {
}
}
- public homeEndKeyBoardEvent(event: Event, home: boolean): void {
+ public homeEndKeyBoardEvent(event: Event): void {
const keyBoardEvent = event as KeyboardEvent;
+ const isHomeKey = keyBoardEvent.key === 'Home';
keyBoardEvent.preventDefault();
const tagName = (keyBoardEvent.target as HTMLElement).tagName;
@@ -94,7 +95,7 @@ export class CarbonEstimationTableComponent {
// Row is focused
const target = keyBoardEvent.target as HTMLTableRowElement;
const table = target.parentElement as HTMLTableElement;
- const newRow = home ? table.rows[0] : this.getLastVisibleRow(table);
+ const newRow = isHomeKey ? table.rows[0] : this.getLastVisibleRow(table);
this.setNewTabIndexAndFocus(newRow, target);
} else if (tagName === 'TD') {
// Cell is focused
@@ -103,12 +104,12 @@ export class CarbonEstimationTableComponent {
if (keyBoardEvent.ctrlKey) {
const table = row.parentElement as HTMLTableElement;
- const newRow = home ? table.rows[0] : this.getLastVisibleRow(table);
+ const newRow = isHomeKey ? table.rows[0] : this.getLastVisibleRow(table);
const newCell = newRow.cells[target.cellIndex];
this.setNewTabIndexAndFocus(newRow, row, false);
this.setNewTabIndexAndFocus(newCell, target);
} else {
- const newCell = home ? row.cells[0] : row.cells[row.cells.length - 1];
+ const newCell = isHomeKey ? row.cells[0] : row.cells[row.cells.length - 1];
this.setNewTabIndexAndFocus(newCell, target);
}
}
From 923d64736da6ba8c0e7a89ea2659cf7eb669b5c0 Mon Sep 17 00:00:00 2001
From: Jareth Main
Date: Tue, 30 Jul 2024 14:56:53 +0100
Subject: [PATCH 36/37] Change empty table check to use table data
---
.../carbon-estimation-table.component.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.html b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
index b0b0c6a9..94b21ff4 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.html
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.html
@@ -6,7 +6,7 @@
Emissions |
- @if (carbonEstimation()) {
+ @if (tableData().length > 0) {
Date: Tue, 30 Jul 2024 15:00:58 +0100
Subject: [PATCH 37/37] Change getTableData to private
---
.../carbon-estimation-table.component.spec.ts | 4 ++--
.../carbon-estimation-table.component.ts | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
index 6de436be..4da6ea57 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.spec.ts
@@ -67,8 +67,8 @@ describe('CarbonEstimationTableComponent', () => {
});
it('should get emissions when getEmissions called', () => {
- const emissions = component.getTableData(component.carbonEstimation());
- expect(emissions.length).toBe(16);
+ const tableData = component.tableData();
+ expect(tableData.length).toBe(16);
});
it('should call toggle when left arrow clicked on expanded parent row', () => {
diff --git a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
index 594f9be1..712345a2 100644
--- a/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
+++ b/src/app/carbon-estimation-table/carbon-estimation-table.component.ts
@@ -194,7 +194,7 @@ export class CarbonEstimationTableComponent {
}
}
- public getTableData(carbonEstimation?: CarbonEstimation): TableItem[] {
+ private getTableData(carbonEstimation?: CarbonEstimation): TableItem[] {
return !carbonEstimation ?
[]
: [
|