File

src/app/modules/filters/filters-content/filters-content.component.ts

Description

Contains components of the filters popup and handles changes in filter settings

Implements

OnChanges

Metadata

Index

Methods
Inputs
Outputs
Accessors

Constructor

constructor(ga: GoogleAnalyticsService)

Creates an instance of filters content component.

Parameters :
Name Type Optional Description
ga GoogleAnalyticsService No

Analytics service

Inputs

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

Outputs

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

Methods

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 :
Name Type Optional Description
value No

The value to be saved for the filter

key string No

The key for the filter to be saved at

Returns : void
updateSearchSelection
updateSearchSelection(items: SpatialSearchFilterItem[])

Emits events for updated searches

Parameters :
Name Type Optional Description
items SpatialSearchFilterItem[] No

New set of selected items

Returns : void
updateSexFromSelection
updateSexFromSelection(items: SpatialSearchFilterItem[])

Updates sex to Both if there is a mismatch between the current selection and the sex

Parameters :
Name Type Optional
items SpatialSearchFilterItem[] No
Returns : void

Accessors

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;
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""