Skip to content

Commit 7377b3f

Browse files
committed
refactor: split up stacked bar chart
Splits up the stacked bar chart into separate files since the component code was getting large.
1 parent 9cb29cb commit 7377b3f

File tree

6 files changed

+176
-181
lines changed

6 files changed

+176
-181
lines changed

report-app/src/app/pages/report-list/report-list.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ScoreBucket, RunGroup } from '../../../../../runner/shared-interfaces';
1212
import {
1313
StackedBarChart,
1414
StackedBarChartData,
15-
} from '../../shared/visualization/stacked-bar-chart';
15+
} from '../../shared/visualization/stacked-bar-chart/stacked-bar-chart';
1616
import { MessageSpinner } from '../../shared/message-spinner';
1717
import { Score } from '../../shared/score/score';
1818
import { ProviderLabel } from '../../shared/provider-label';

report-app/src/app/pages/report-viewer/report-viewer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { ReportsFetcher } from '../../services/reports-fetcher';
2828
import {
2929
StackedBarChart,
3030
StackedBarChartData,
31-
} from '../../shared/visualization/stacked-bar-chart';
31+
} from '../../shared/visualization/stacked-bar-chart/stacked-bar-chart';
3232
import { formatFile } from './formatter';
3333
import { FailedChecksFilter } from './failed-checks-filter';
3434
import { MessageSpinner } from '../../shared/message-spinner';

report-app/src/app/shared/visualization/stacked-bar-chart.ts

Lines changed: 0 additions & 179 deletions
This file was deleted.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<div class="chart-container">
2+
<!-- The main stacked bar -->
3+
<div class="stacked-bar" [class.compact]="compact()">
4+
@for (item of data(); track $index) {
5+
@if (item.value > 0) {
6+
<div
7+
class="segment"
8+
[style.width.%]="asPercent(item.value)"
9+
[style.background-color]="item.color"
10+
(click)="toggleDisplayMode()"
11+
[attr.data-tooltip]="showLegend() ? null : item.label"
12+
>
13+
{{ getItemDisplayValue(item) }}
14+
</div>
15+
}
16+
}
17+
</div>
18+
19+
<!-- The legend for the bar chart -->
20+
@if (showLegend()) {
21+
<div class="legend">
22+
@for (item of data(); track $index) {
23+
<div class="legend-item">
24+
<span
25+
class="legend-color"
26+
[style.background-color]="item.color"
27+
></span>
28+
{{ item.label }}
29+
</div>
30+
}
31+
</div>
32+
}
33+
</div>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
:host {
2+
display: block;
3+
width: 100%;
4+
}
5+
6+
.chart-container {
7+
width: 100%;
8+
background-color: var(--card-bg-color);
9+
}
10+
11+
.stacked-bar {
12+
display: flex;
13+
height: 45px;
14+
margin-bottom: 1rem;
15+
16+
&.compact {
17+
margin-bottom: 0;
18+
height: 24px;
19+
20+
.segment {
21+
font-size: 12px;
22+
}
23+
24+
& + .legend {
25+
margin-top: 0.5rem;
26+
}
27+
}
28+
}
29+
30+
.segment {
31+
position: relative;
32+
display: flex;
33+
align-items: center;
34+
justify-content: center;
35+
color: white;
36+
font-size: 14px;
37+
font-weight: 500;
38+
transition: filter 0.2s ease-in-out;
39+
min-width: 50px;
40+
41+
&:first-child {
42+
border-top-left-radius: 8px;
43+
border-bottom-left-radius: 8px;
44+
}
45+
46+
&:last-child {
47+
border-top-right-radius: 8px;
48+
border-bottom-right-radius: 8px;
49+
}
50+
51+
&:hover {
52+
filter: brightness(1.1);
53+
}
54+
55+
&[data-tooltip]::before {
56+
content: attr(data-tooltip); // Use a data attribute for the text
57+
position: absolute;
58+
bottom: 110%; // Position it above the segment
59+
left: 50%;
60+
transform: translateX(-50%);
61+
background-color: var(--tooltip-background-color);
62+
color: var(--tooltip-text-color);
63+
padding: 6px 12px;
64+
border-radius: 6px;
65+
font-size: 13px;
66+
white-space: nowrap;
67+
opacity: 0;
68+
visibility: hidden;
69+
transition:
70+
opacity 0.2s ease-in-out,
71+
visibility 0.2s ease-in-out;
72+
z-index: 10;
73+
}
74+
75+
&[data-tooltip]:hover::before {
76+
opacity: 1;
77+
visibility: visible;
78+
}
79+
}
80+
81+
.legend {
82+
display: flex;
83+
justify-content: center;
84+
gap: 1.5rem;
85+
}
86+
87+
.legend-item {
88+
display: flex;
89+
align-items: center;
90+
font-size: 14px;
91+
color: var(--text-secondary);
92+
white-space: nowrap;
93+
}
94+
95+
.legend-color {
96+
width: 14px;
97+
height: 14px;
98+
border-radius: 4px;
99+
margin-right: 8px;
100+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { Component, computed, input, signal } from '@angular/core';
2+
3+
export type StackedBarChartData = Array<{
4+
label: string;
5+
color: string;
6+
value: number;
7+
}>;
8+
9+
@Component({
10+
selector: 'stacked-bar-chart',
11+
styleUrl: 'stacked-bar-chart.scss',
12+
templateUrl: 'stacked-bar-chart.html',
13+
})
14+
export class StackedBarChart {
15+
data = input.required<StackedBarChartData>();
16+
compact = input(false);
17+
showLegend = input(true);
18+
19+
total = computed(() =>
20+
this.data().reduce((acc, item) => acc + item.value, 0)
21+
);
22+
23+
protected displayPercentage = signal(false);
24+
25+
asPercent(value: number) {
26+
if (this.total() === 0) return 0;
27+
const percentage = (value / this.total()) * 100;
28+
return parseFloat(percentage.toFixed(percentage % 1 === 0 ? 0 : 1));
29+
}
30+
31+
toggleDisplayMode(): void {
32+
this.displayPercentage.update((current) => !current);
33+
}
34+
35+
getItemDisplayValue(item: StackedBarChartData[0]): string {
36+
if (item.value === 0) return '';
37+
return this.displayPercentage()
38+
? `${this.asPercent(item.value)}%`
39+
: `${item.value}`;
40+
}
41+
}

0 commit comments

Comments
 (0)