src/app/modules/filters/filters-content/filters-content.component.ts
Contains components of the filters popup and handles changes in filter settings
OnChanges
changeDetection | ChangeDetectionStrategy.OnPush |
selector | ccf-filters-content |
styleUrls | ./filters-content.component.scss |
templateUrl | ./filters-content.component.html |
Methods |
Inputs |
Outputs |
Accessors |
constructor(ga: GoogleAnalyticsService)
|
||||||||
Creates an instance of filters content component.
Parameters :
|
filters | |
Type : Record<string | | []>
|
|
Allows the filters to be set from outside the component |
hidden | |
Type : boolean
|
|
Determines if the filters are visible |
providerFilters | |
Type : string[]
|
|
List of providers in the data |
spatialSearchFilters | |
Type : SpatialSearchFilterItem[]
|
|
Default value : []
|
|
List of spatial searches |
technologyFilters | |
Type : string[]
|
|
List of technologies in the data |
applyFilters | |
Type : EventEmitter
|
|
Emits the filters to be applied |
filtersChange | |
Type : EventEmitter
|
|
Emits the filter change when they happen |
spatialSearchRemoved | |
Type : EventEmitter
|
|
Emits when a spatial search is removed/deleted |
spatialSearchSelected | |
Type : EventEmitter
|
|
Emits when a spatial search is selected/deselected |
applyButtonClick |
applyButtonClick()
|
Emits the current filters when the apply button is clicked
Returns :
void
|
refreshFilters |
refreshFilters()
|
Refreshes all filter settings
Returns :
void
|
updateFilter | ||||||||||||
updateFilter(value, key: string)
|
||||||||||||
Updates the filter object with a new key/value
Parameters :
Returns :
void
|
updateSearchSelection | ||||||||
updateSearchSelection(items: SpatialSearchFilterItem[])
|
||||||||
Emits events for updated searches
Parameters :
Returns :
void
|
updateSexFromSelection | ||||||
updateSexFromSelection(items: SpatialSearchFilterItem[])
|
||||||
Updates sex to
Parameters :
Returns :
void
|
sex |
getsex()
|
ageRange |
getageRange()
|
bmiRange |
getbmiRange()
|
technologies |
gettechnologies()
|
tmc |
gettmc()
|
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges,
} from '@angular/core';
import { GoogleAnalyticsService } from 'ngx-google-analytics';
import { DEFAULT_FILTER } from '../../../core/store/data/data.state';
import { SpatialSearchFilterItem } from '../../../core/store/spatial-search-filter/spatial-search-filter.state';
import { Sex } from '../../../shared/components/spatial-search-config/spatial-search-config.component';
/**
* Contains components of the filters popup and handles changes in filter settings
*/
@Component({
selector: 'ccf-filters-content',
templateUrl: './filters-content.component.html',
styleUrls: ['./filters-content.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FiltersContentComponent implements OnChanges {
/**
* Determines if the filters are visible
*/
@Input() hidden!: boolean;
/**
* Allows the filters to be set from outside the component
*/
@Input() filters?: Record<string, unknown | unknown[]>;
/**
* List of technologies in the data
*/
@Input() technologyFilters!: string[];
/**
* List of providers in the data
*/
@Input() providerFilters!: string[];
/**
* List of spatial searches
*/
@Input() spatialSearchFilters: SpatialSearchFilterItem[] = [];
/**
* Emits the filter change when they happen
*/
@Output() readonly filtersChange = new EventEmitter<Record<string, unknown>>();
/**
* Emits when a spatial search is selected/deselected
*/
@Output() readonly spatialSearchSelected = new EventEmitter<SpatialSearchFilterItem[]>();
/**
* Emits when a spatial search is removed/deleted
*/
@Output() readonly spatialSearchRemoved = new EventEmitter<string>();
/**
* Emits the filters to be applied
*/
@Output() readonly applyFilters = new EventEmitter<Record<string, unknown>>();
get sex(): Sex {
return this.getFilterValue<string>('sex', 'male')?.toLowerCase() as Sex;
}
get ageRange(): number[] {
return this.getFilterValue<number[]>('ageRange', []);
}
get bmiRange(): number[] {
return this.getFilterValue<number[]>('bmiRange', []);
}
get technologies(): string[] {
return this.getFilterValue<string[]>('technologies', []);
}
get tmc(): string[] {
return this.getFilterValue<string[]>('tmc', []);
}
/**
* Creates an instance of filters content component.
*
* @param ga Analytics service
*/
constructor(private readonly ga: GoogleAnalyticsService) {}
/**
* Handle input changes
*/
ngOnChanges(changes: SimpleChanges): void {
if ('spatialSearchFilters' in changes) {
this.updateSexFromSelection(this.spatialSearchFilters.filter((item) => item.selected));
}
}
/**
* Updates the filter object with a new key/value
*
* @param value The value to be saved for the filter
* @param key The key for the filter to be saved at
*/
updateFilter(value: unknown, key: string): void {
this.filters = { ...this.filters, [key]: value };
this.ga.event('filter_update', 'filter_content', `${key}:${value}`);
this.filtersChange.emit(this.filters);
}
/**
* Emits the current filters when the apply button is clicked
*/
applyButtonClick(): void {
this.updateSearchSelection(this.spatialSearchFilters.filter((item) => item.selected));
this.ga.event('filters_applied', 'filter_content');
this.applyFilters.emit(this.filters);
}
/**
* Refreshes all filter settings
*/
refreshFilters(): void {
this.filters = JSON.parse(JSON.stringify(DEFAULT_FILTER));
this.ga.event('filters_reset', 'filter_content');
this.spatialSearchSelected.emit([]);
this.filtersChange.emit(this.filters);
}
/**
* Emits events for updated searches
*
* @param items New set of selected items
*/
updateSearchSelection(items: SpatialSearchFilterItem[]): void {
const searches = items.map((item) => item.search);
this.spatialSearchSelected.emit(items);
this.updateFilter(searches, 'spatialSearches');
this.updateSexFromSelection(items);
}
/**
* Updates sex to `Both` if there is a mismatch between the current selection and the sex
*/
updateSexFromSelection(items: SpatialSearchFilterItem[]): void {
const currentSex = this.sex;
const selectedSexes = new Set(items.map((item) => item.sex));
if (items.length > 0 && (selectedSexes.size > 1 || !selectedSexes.has(currentSex))) {
this.updateFilter('Both', 'sex');
}
}
private getFilterValue<T>(key: string, defaultValue: T): T {
return (this.filters?.[key] as T | undefined) ?? defaultValue;
}
}
<div class="patient-filters" [class.hidden]="hidden">
<ccf-dropdown
label="Sex"
[options]="['Both', 'Male', 'Female']"
[selection]="sex"
(selectionChange)="updateFilter($event, 'sex')"
></ccf-dropdown>
<ccf-dual-slider
label="Age"
[valueRange]="[1, 110]"
[selection]="ageRange"
(selectionChange)="updateFilter($event, 'ageRange')"
></ccf-dual-slider>
<ccf-dual-slider
label="BMI"
[valueRange]="[13, 83]"
[selection]="bmiRange"
(selectionChange)="updateFilter($event, 'bmiRange')"
></ccf-dual-slider>
</div>
<div class="filter assays" [class.hidden]="hidden">
<ccf-checkbox
label="Assay Types"
[columns]="5"
[options]="technologyFilters"
[selection]="technologies"
(selectionChange)="updateFilter($event, 'technologies')"
></ccf-checkbox>
</div>
<div class="filter providers" [class.hidden]="hidden">
<ccf-checkbox
label="Tissue Providers"
[columns]="3"
[options]="providerFilters"
[selection]="tmc"
(selectionChange)="updateFilter($event, 'tmc')"
></ccf-checkbox>
</div>
<div *ngIf="spatialSearchFilters.length > 0" class="filter spatial-locations" [class.hidden]="hidden">
<ccf-spatial-search-list
label="Spatial Locations"
[items]="spatialSearchFilters"
(selectionChanged)="updateSearchSelection($event)"
(itemRemoved)="spatialSearchRemoved.emit($event.id)"
>
</ccf-spatial-search-list>
</div>
<div class="button-container" [class.hidden]="hidden">
<ccf-run-spatial-search></ccf-run-spatial-search>
<div class="right-group">
<button class="outline-button" mat-button (click)="applyButtonClick()">Apply Filters</button>
<div class="refresh-icon">
<mat-icon class="icon refresh" (click)="refreshFilters()">refresh</mat-icon>
</div>
</div>
</div>
./filters-content.component.scss
.filter {
margin-top: 1.5rem;
&.assays {
.option {
margin-right: 0 rem;
width: 25%;
}
}
::ng-deep ccf-checkbox {
.options-container {
position: relative;
left: -0.5rem;
width: calc(100% + 0.5rem);
.mdc-checkbox {
padding: 0.5rem;
width: 1rem;
height: 1rem;
flex: 0 0 1rem;
.mdc-checkbox__native-control {
height: 2rem;
width: 2rem;
}
.mdc-checkbox__background {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.mdc-label {
font-weight: 400;
padding-left: 0;
text-wrap: nowrap;
}
}
}
}
.button-container {
display: flex;
justify-content: space-between;
margin-top: 1.5rem;
.right-group {
display: flex;
flex-direction: row-reverse;
align-items: center;
.outline-button {
border-width: 1px;
border-style: solid;
width: 11rem;
height: 100%;
box-shadow: 0.1rem 0.1rem 0.2rem 0rem #0000001d;
}
.refresh-icon {
padding: 0.25rem;
border-radius: 0.25rem;
margin-right: 1rem;
display: flex;
justify-content: center;
align-items: center;
transition: 0.6s;
.refresh {
cursor: pointer;
transition: 0.6s;
transition-property: background;
}
}
}
}
.patient-filters {
display: flex;
padding-top: 0.5rem;
gap: 2rem;
ccf-dual-slider {
margin-left: 0.5rem;
}
}
.hidden {
opacity: 0;
transition-duration: 0.2s;
}
ccf-dropdown,
ccf-dual-slider {
width: 10rem;
}