-
+
+
+
+ @if (showExpandedSection()) {
+
+
+
+
+ {{ title() | translate }}
+
+
+
+ @for (label of labels(); let i = $index; track i) {
+
+
+ {{ label }}
+
+ }
+
+
+
+
+
+ }
}
diff --git a/src/app/shared/components/bar-chart/bar-chart.component.scss b/src/app/shared/components/bar-chart/bar-chart.component.scss
index bcbc327cf..478abc91c 100644
--- a/src/app/shared/components/bar-chart/bar-chart.component.scss
+++ b/src/app/shared/components/bar-chart/bar-chart.component.scss
@@ -1,15 +1,4 @@
:host {
- // display: block;
- // width: 100%;
-}
-
-.chart-title {
- font-size: 1.7rem;
-}
-
-.chart {
display: block;
- width: 100%;
height: 100%;
- margin-top: 1.7rem;
}
diff --git a/src/app/shared/components/bar-chart/bar-chart.component.ts b/src/app/shared/components/bar-chart/bar-chart.component.ts
index 898c5da11..59287845f 100644
--- a/src/app/shared/components/bar-chart/bar-chart.component.ts
+++ b/src/app/shared/components/bar-chart/bar-chart.component.ts
@@ -1,5 +1,6 @@
import { TranslatePipe } from '@ngx-translate/core';
+import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion';
import { ChartModule } from 'primeng/chart';
import { isPlatformBrowser } from '@angular/common';
@@ -15,6 +16,7 @@ import {
} from '@angular/core';
import { DatasetInput } from '@osf/shared/models';
+import { PIE_CHART_PALETTE } from '@shared/utils';
import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.component';
@@ -22,7 +24,15 @@ import { ChartData, ChartOptions } from 'chart.js';
@Component({
selector: 'osf-bar-chart',
- imports: [ChartModule, TranslatePipe, LoadingSpinnerComponent],
+ imports: [
+ ChartModule,
+ TranslatePipe,
+ LoadingSpinnerComponent,
+ AccordionContent,
+ AccordionHeader,
+ AccordionPanel,
+ Accordion,
+ ],
templateUrl: './bar-chart.component.html',
styleUrl: './bar-chart.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -34,7 +44,10 @@ export class BarChartComponent implements OnInit {
datasets = input
([]);
showLegend = input(false);
showGrid = input(false);
+ showTicks = input(true);
+
orientation = input<'horizontal' | 'vertical'>('horizontal');
+ showExpandedSection = input(false);
protected options = signal({});
protected data = signal({} as ChartData);
@@ -75,6 +88,10 @@ export class BarChartComponent implements OnInit {
});
}
+ getColor(index: number): string {
+ return PIE_CHART_PALETTE[index % PIE_CHART_PALETTE.length];
+ }
+
private setChartOptions(textColorSecondary: string, surfaceBorder: string) {
this.options.set({
indexAxis: this.orientation() === 'horizontal' ? 'y' : 'x',
@@ -89,6 +106,7 @@ export class BarChartComponent implements OnInit {
scales: {
x: {
ticks: {
+ display: this.showTicks(),
color: textColorSecondary,
},
grid: {
@@ -98,6 +116,7 @@ export class BarChartComponent implements OnInit {
},
y: {
ticks: {
+ display: this.showTicks(),
color: textColorSecondary,
},
grid: {
diff --git a/src/app/shared/components/doughnut-chart/doughnut-chart.component.html b/src/app/shared/components/doughnut-chart/doughnut-chart.component.html
new file mode 100644
index 000000000..e8d3f10e2
--- /dev/null
+++ b/src/app/shared/components/doughnut-chart/doughnut-chart.component.html
@@ -0,0 +1,35 @@
+@if (!showExpandedSection()) {
+ {{ title() | translate }}
+}
+
+@if (isLoading()) {
+
+} @else {
+
+
+
+ @if (showExpandedSection()) {
+
+
+
+
+ {{ title() | translate }}
+
+
+
+ @for (label of labels(); let i = $index; track i) {
+
+
+ {{ label }}
+
+ }
+
+
+
+
+
+ }
+
+}
diff --git a/src/app/shared/components/doughnut-chart/doughnut-chart.component.scss b/src/app/shared/components/doughnut-chart/doughnut-chart.component.scss
new file mode 100644
index 000000000..478abc91c
--- /dev/null
+++ b/src/app/shared/components/doughnut-chart/doughnut-chart.component.scss
@@ -0,0 +1,4 @@
+:host {
+ display: block;
+ height: 100%;
+}
diff --git a/src/app/shared/components/doughnut-chart/doughnut-chart.component.spec.ts b/src/app/shared/components/doughnut-chart/doughnut-chart.component.spec.ts
new file mode 100644
index 000000000..0e8406fba
--- /dev/null
+++ b/src/app/shared/components/doughnut-chart/doughnut-chart.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DoughnutChartComponent } from './doughnut-chart.component';
+
+describe('PieChartComponent', () => {
+ let component: DoughnutChartComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [DoughnutChartComponent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(DoughnutChartComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/shared/components/doughnut-chart/doughnut-chart.component.ts b/src/app/shared/components/doughnut-chart/doughnut-chart.component.ts
new file mode 100644
index 000000000..99e115110
--- /dev/null
+++ b/src/app/shared/components/doughnut-chart/doughnut-chart.component.ts
@@ -0,0 +1,97 @@
+import { TranslatePipe } from '@ngx-translate/core';
+
+import { Accordion, AccordionContent, AccordionHeader, AccordionPanel } from 'primeng/accordion';
+import { ChartModule } from 'primeng/chart';
+
+import { isPlatformBrowser } from '@angular/common';
+import {
+ ChangeDetectionStrategy,
+ ChangeDetectorRef,
+ Component,
+ inject,
+ input,
+ OnInit,
+ PLATFORM_ID,
+ signal,
+} from '@angular/core';
+
+import { DatasetInput } from '@osf/shared/models';
+import { PIE_CHART_PALETTE } from '@osf/shared/utils';
+
+import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.component';
+
+import { ChartData, ChartOptions } from 'chart.js';
+
+@Component({
+ selector: 'osf-doughnut-chart',
+ imports: [
+ ChartModule,
+ TranslatePipe,
+ LoadingSpinnerComponent,
+ Accordion,
+ AccordionHeader,
+ AccordionPanel,
+ AccordionContent,
+ ],
+ templateUrl: './doughnut-chart.component.html',
+ styleUrl: './doughnut-chart.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class DoughnutChartComponent implements OnInit {
+ private readonly platformId = inject(PLATFORM_ID);
+ private readonly cd = inject(ChangeDetectorRef);
+
+ isLoading = input(false);
+ title = input('');
+ labels = input([]);
+ datasets = input([]);
+ showLegend = input(false);
+ showExpandedSection = input(false);
+
+ protected options = signal({});
+ protected data = signal({} as ChartData);
+
+ ngOnInit() {
+ this.initChart();
+ }
+
+ initChart() {
+ if (isPlatformBrowser(this.platformId)) {
+ this.setChartData();
+ this.setChartOptions();
+
+ this.cd.markForCheck();
+ }
+ }
+
+ getColor(index: number): string {
+ return PIE_CHART_PALETTE[index % PIE_CHART_PALETTE.length];
+ }
+
+ private setChartData() {
+ const chartDatasets = this.datasets().map((dataset) => ({
+ label: dataset.label,
+ data: dataset.data,
+ backgroundColor: dataset?.color || PIE_CHART_PALETTE,
+ borderWidth: 0,
+ }));
+
+ this.data.set({
+ labels: this.labels(),
+ datasets: chartDatasets,
+ });
+ }
+
+ private setChartOptions() {
+ this.options.set({
+ maintainAspectRatio: true,
+ responsive: true,
+ plugins: {
+ legend: {
+ display: this.showLegend(),
+ position: 'bottom',
+ },
+ },
+ });
+ }
+}
diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts
index 80d1049bc..762c98247 100644
--- a/src/app/shared/components/index.ts
+++ b/src/app/shared/components/index.ts
@@ -28,6 +28,7 @@ export { SearchHelpTutorialComponent } from './search-help-tutorial/search-help-
export { SearchInputComponent } from './search-input/search-input.component';
export { SearchResultsContainerComponent } from './search-results-container/search-results-container.component';
export { SelectComponent } from './select/select.component';
+export { StatisticCardComponent } from './statistic-card/statistic-card.component';
export { StepperComponent } from './stepper/stepper.component';
export { SubHeaderComponent } from './sub-header/sub-header.component';
export { SubjectsComponent } from './subjects/subjects.component';
diff --git a/src/app/shared/components/line-chart/line-chart.component.html b/src/app/shared/components/line-chart/line-chart.component.html
index ff756e454..961d8bf4d 100644
--- a/src/app/shared/components/line-chart/line-chart.component.html
+++ b/src/app/shared/components/line-chart/line-chart.component.html
@@ -1,9 +1,9 @@
-{{ title() | translate }}
+{{ title() | translate }}
@if (isLoading()) {
} @else {
}
diff --git a/src/app/shared/components/line-chart/line-chart.component.scss b/src/app/shared/components/line-chart/line-chart.component.scss
index bcbc327cf..5d4e87f30 100644
--- a/src/app/shared/components/line-chart/line-chart.component.scss
+++ b/src/app/shared/components/line-chart/line-chart.component.scss
@@ -1,15 +1,3 @@
:host {
- // display: block;
- // width: 100%;
-}
-
-.chart-title {
- font-size: 1.7rem;
-}
-
-.chart {
display: block;
- width: 100%;
- height: 100%;
- margin-top: 1.7rem;
}
diff --git a/src/app/shared/components/pie-chart/pie-chart.component.html b/src/app/shared/components/pie-chart/pie-chart.component.html
index 2491f813b..48c7addd7 100644
--- a/src/app/shared/components/pie-chart/pie-chart.component.html
+++ b/src/app/shared/components/pie-chart/pie-chart.component.html
@@ -1,9 +1,9 @@
-{{ title() | translate }}
+{{ title() | translate }}
@if (isLoading()) {
} @else {
}
diff --git a/src/app/shared/components/pie-chart/pie-chart.component.scss b/src/app/shared/components/pie-chart/pie-chart.component.scss
index db8aeaa21..478abc91c 100644
--- a/src/app/shared/components/pie-chart/pie-chart.component.scss
+++ b/src/app/shared/components/pie-chart/pie-chart.component.scss
@@ -1,13 +1,4 @@
:host {
display: block;
- // width: 100%;
-}
-
-.chart-title {
- font-size: 1.7rem;
-}
-
-.chart {
- display: block;
- margin-top: 1.7rem;
+ height: 100%;
}
diff --git a/src/app/shared/components/search-results-container/search-results-container.component.html b/src/app/shared/components/search-results-container/search-results-container.component.html
index 7e98a712a..347523cbf 100644
--- a/src/app/shared/components/search-results-container/search-results-container.component.html
+++ b/src/app/shared/components/search-results-container/search-results-container.component.html
@@ -33,29 +33,21 @@
>
@if (isAnyFilterOptions()) {
-
+ >
}
-
+ >
diff --git a/src/app/shared/components/search-results-container/search-results-container.component.ts b/src/app/shared/components/search-results-container/search-results-container.component.ts
index 070ae6d3f..b4fd1b5b6 100644
--- a/src/app/shared/components/search-results-container/search-results-container.component.ts
+++ b/src/app/shared/components/search-results-container/search-results-container.component.ts
@@ -4,7 +4,6 @@ import { Button } from 'primeng/button';
import { DataView } from 'primeng/dataview';
import { Select } from 'primeng/select';
-import { NgOptimizedImage } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
import { FormsModule } from '@angular/forms';
@@ -18,16 +17,7 @@ import { SelectComponent } from '../select/select.component';
@Component({
selector: 'osf-search-results-container',
- imports: [
- FormsModule,
- NgOptimizedImage,
- Button,
- DataView,
- Select,
- ResourceCardComponent,
- TranslatePipe,
- SelectComponent,
- ],
+ imports: [FormsModule, Button, DataView, Select, ResourceCardComponent, TranslatePipe, SelectComponent],
templateUrl: './search-results-container.component.html',
styleUrl: './search-results-container.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
diff --git a/src/app/shared/components/statistic-card/statistic-card.component.html b/src/app/shared/components/statistic-card/statistic-card.component.html
new file mode 100644
index 000000000..00094df87
--- /dev/null
+++ b/src/app/shared/components/statistic-card/statistic-card.component.html
@@ -0,0 +1,6 @@
+