From e88499cbafb03f929321e4594b892aa8cd68ccb6 Mon Sep 17 00:00:00 2001 From: James Fusia Date: Thu, 6 Jun 2024 13:53:51 -0400 Subject: [PATCH] saving progress, lots of charting decisions made --- src/app/app.component.html | 22 ++++- src/app/app.component.ts | 35 ++++++- .../cohort-section.component.html | 1 + .../cohort-section.component.scss | 0 .../cohort-section.component.spec.ts | 23 +++++ .../cohort-section.component.ts | 80 ++++++++++++++++ src/app/scores/scores.component.html | 1 + src/app/scores/scores.component.scss | 0 src/app/scores/scores.component.spec.ts | 23 +++++ src/app/scores/scores.component.ts | 92 +++++++++++++++++++ src/components/chart/chart-config.ts | 7 ++ src/components/chart/chart-type.ts | 30 +++++- src/components/chart/chart.component.ts | 63 ++++++------- .../chart/services/chart-config.service.ts | 30 ++++++ .../chart/services/dataset.service.ts | 54 +++++++++++ .../chart/services/label.service.ts | 33 +++++++ .../chart/services/options.service.ts | 27 ++++++ .../chart/services/scale.service.ts | 59 ++++++++++++ src/components/select/select.component.html | 8 +- src/components/select/select.component.ts | 22 ++++- src/components/table/table.component.html | 3 +- src/components/table/table.component.ts | 3 +- src/services/data.service.ts | 79 ++++++++-------- src/services/filters.service.ts | 4 + 24 files changed, 612 insertions(+), 87 deletions(-) create mode 100644 src/app/cohort-section/cohort-section.component.html create mode 100644 src/app/cohort-section/cohort-section.component.scss create mode 100644 src/app/cohort-section/cohort-section.component.spec.ts create mode 100644 src/app/cohort-section/cohort-section.component.ts create mode 100644 src/app/scores/scores.component.html create mode 100644 src/app/scores/scores.component.scss create mode 100644 src/app/scores/scores.component.spec.ts create mode 100644 src/app/scores/scores.component.ts create mode 100644 src/components/chart/chart-config.ts create mode 100644 src/components/chart/services/chart-config.service.ts create mode 100644 src/components/chart/services/dataset.service.ts create mode 100644 src/components/chart/services/label.service.ts create mode 100644 src/components/chart/services/options.service.ts create mode 100644 src/components/chart/services/scale.service.ts diff --git a/src/app/app.component.html b/src/app/app.component.html index 891cfd7..86b775a 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,10 +1,24 @@ -
+ + + + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 4803758..b125ba8 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -6,24 +6,49 @@ import { DataService } from '../services/data.service'; import { TableComponent } from '../components/table/table.component'; import { Header } from '../components/table/header'; import { FilterService } from '../services/filters.service'; +import { Observable } from 'rxjs'; +import { Milestone } from '../enums/milestone'; +import { ScoresComponent } from './scores/scores.component'; +import { CommonModule } from '@angular/common'; +import { CohortSectionComponent } from './cohort-section/cohort-section.component'; +import { KeyValue } from '../models/key-value'; @Component({ selector: 'app-root', standalone: true, - imports: [RouterOutlet, ChartComponent, SelectComponent, TableComponent], + imports: [ + CommonModule, + RouterOutlet, + ChartComponent, + SelectComponent, + TableComponent, + ScoresComponent, + CohortSectionComponent, + ], templateUrl: './app.component.html', styleUrl: './app.component.scss', }) export class AppComponent implements OnInit { title = 'Georgia Milestones'; + county = '644'; + school = ['3067', '0605']; + grades = [3, 4, 5]; + cohorts: string[] = []; + data = inject(DataService); filter = inject(FilterService); - header = [new Header({ label: 'School', source: 'school' })]; - rows: Record[] = []; + header = [ + { label: 'School', source: Milestone.School }, + { label: 'Grade', source: Milestone.Grade }, + ].map((data) => new Header(data)); + rows: Observable[]> = this.data.Data; ngOnInit(): void { + this.data.Cohorts.subscribe((kv: KeyValue[]) => { + this.cohorts = kv.map((i: KeyValue) => i.key); + }); // this.filter.filters$.subscribe((filters: KeyValue[]) => { // this.data.Data.pipe(take(1)).subscribe((data: Milestone[]) => { // const result = data.filter((d: Milestone) => { @@ -46,4 +71,8 @@ export class AppComponent implements OnInit { // }); // }); } + + onReset(event: any): void { + this.filter.reset(); + } } diff --git a/src/app/cohort-section/cohort-section.component.html b/src/app/cohort-section/cohort-section.component.html new file mode 100644 index 0000000..a09db69 --- /dev/null +++ b/src/app/cohort-section/cohort-section.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/cohort-section/cohort-section.component.scss b/src/app/cohort-section/cohort-section.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/cohort-section/cohort-section.component.spec.ts b/src/app/cohort-section/cohort-section.component.spec.ts new file mode 100644 index 0000000..a1f5778 --- /dev/null +++ b/src/app/cohort-section/cohort-section.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CohortSectionComponent } from './cohort-section.component'; + +describe('CohortSectionComponent', () => { + let component: CohortSectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CohortSectionComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CohortSectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/cohort-section/cohort-section.component.ts b/src/app/cohort-section/cohort-section.component.ts new file mode 100644 index 0000000..a295e84 --- /dev/null +++ b/src/app/cohort-section/cohort-section.component.ts @@ -0,0 +1,80 @@ +import { Component, Input, inject } from '@angular/core'; +import { ChartComponent } from '../../components/chart/chart.component'; +import { CommonModule } from '@angular/common'; +import { Milestone } from '../../enums/milestone'; +import { + ChartAxisPosition, + ChartType, +} from '../../components/chart/chart-type'; +import { ChartConfig } from '../../components/chart/chart-config'; +import { DataService } from '../../services/data.service'; + +@Component({ + selector: 'fbi-cohort-section', + standalone: true, + imports: [CommonModule, ChartComponent], + templateUrl: './cohort-section.component.html', + styleUrl: './cohort-section.component.scss', +}) +export class CohortSectionComponent { + @Input() county: string | string[] = '644'; + @Input() school: string | string[] = '3067'; + @Input() cohort!: string | string[]; + + chart: ChartConfig = { + type: ChartType.Line, + axis: [ + { + label: 'Grade', + source: Milestone.Grade, + position: ChartAxisPosition.Bottom, + stacked: true, + }, + { + label: [ + 'Beginning Learner', + 'Developing Learner', + 'Proficient Learner', + 'Distinguished Learner', + ], + source: [ + Milestone.ELA0, + Milestone.ELA1, + Milestone.ELA2, + Milestone.ELA3, + ], + position: ChartAxisPosition.Left, + fill: true, + stacked: true, + stack: 'ELA', + min: 0, + max: 100, + }, + ], + }; + data: Record[] = []; + + private dataService = inject(DataService); + + ngOnInit(): void { + const county = Array.isArray(this.county) ? this.county : [this.county]; + const school = Array.isArray(this.school) ? this.school : [this.school]; + const cohort = ( + Array.isArray(this.cohort) ? this.cohort : [this.cohort] + ).map((v: string) => parseInt(v)); + + this.dataService.Data.subscribe((data: Record[]) => { + this.data = data + .filter( + (d: Record) => + county.includes(d[Milestone.County]) && + school.includes(d[Milestone.School]) && + cohort.includes(d[Milestone.Cohort]) + ) + .sort( + (a: Record, b: Record) => + a['grade'] - b['grade'] + ); + }); + } +} diff --git a/src/app/scores/scores.component.html b/src/app/scores/scores.component.html new file mode 100644 index 0000000..ec198d3 --- /dev/null +++ b/src/app/scores/scores.component.html @@ -0,0 +1 @@ + diff --git a/src/app/scores/scores.component.scss b/src/app/scores/scores.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/scores/scores.component.spec.ts b/src/app/scores/scores.component.spec.ts new file mode 100644 index 0000000..3c5e6c0 --- /dev/null +++ b/src/app/scores/scores.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ScoresComponent } from './scores.component'; + +describe('ScoresComponent', () => { + let component: ScoresComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ScoresComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ScoresComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/scores/scores.component.ts b/src/app/scores/scores.component.ts new file mode 100644 index 0000000..0449387 --- /dev/null +++ b/src/app/scores/scores.component.ts @@ -0,0 +1,92 @@ +import { CommonModule } from '@angular/common'; +import { Component, Input, OnInit, inject } from '@angular/core'; +import { ChartComponent } from '../../components/chart/chart.component'; +import { + ChartAxisPosition, + ChartType, +} from '../../components/chart/chart-type'; +import { ChartConfig } from '../../components/chart/chart-config'; +import { DataService } from '../../services/data.service'; +import { Milestone } from '../../enums/milestone'; + +@Component({ + selector: 'fbi-scores', + standalone: true, + imports: [CommonModule, ChartComponent], + templateUrl: './scores.component.html', + styleUrl: './scores.component.scss', +}) +export class ScoresComponent implements OnInit { + @Input() county: string = '644'; + @Input() school: string = '3067'; + @Input() grade!: number; + + chart: ChartConfig = { + type: ChartType.Bar, + axis: [ + { + label: 'Year', + source: Milestone.Year, + position: ChartAxisPosition.Bottom, + stacked: true, + }, + { + label: [ + 'Beginning Learner', + 'Developing Learner', + 'Proficient Learner', + 'Distinguished Learner', + ], + source: [ + Milestone.ELA0, + Milestone.ELA1, + Milestone.ELA2, + Milestone.ELA3, + ], + position: ChartAxisPosition.Left, + stacked: true, + stack: 'ELA', + min: 0, + max: 100, + }, + { + label: [ + 'Beginning Learner', + 'Developing Learner', + 'Proficient Learner', + 'Distinguished Learner', + ], + source: [ + Milestone.Math0, + Milestone.Math1, + Milestone.Math2, + Milestone.Math3, + ], + position: ChartAxisPosition.Right, + stacked: true, + stack: 'Math', + min: 0, + max: 100, + }, + ], + }; + + data: Record[] = []; + + private dataService = inject(DataService); + + ngOnInit(): void { + const county = this.county; + const school = this.school; + const grade = this.grade; + + this.dataService.Data.subscribe((data: Record[]) => { + this.data = data.filter( + (d: Record) => + d[Milestone.County] === county && + d[Milestone.School] === school && + d[Milestone.Grade] === grade + ); + }); + } +} diff --git a/src/components/chart/chart-config.ts b/src/components/chart/chart-config.ts new file mode 100644 index 0000000..fa35301 --- /dev/null +++ b/src/components/chart/chart-config.ts @@ -0,0 +1,7 @@ +import { ChartType } from 'chart.js'; +import { ChartAxis } from './chart-type'; + +export interface ChartConfig { + type: ChartType; + axis: ChartAxis[]; +} diff --git a/src/components/chart/chart-type.ts b/src/components/chart/chart-type.ts index f9ae496..fa550bf 100644 --- a/src/components/chart/chart-type.ts +++ b/src/components/chart/chart-type.ts @@ -9,14 +9,30 @@ export enum ChartType { Radar = 'radar', } +export enum ChartAxisPosition { + Top = 'top', + Right = 'right', + Bottom = 'bottom', + Left = 'left', + Center = 'center', +} + export interface IDataset { type?: ChartType; borderColor?: string; label?: string; - data: string[]; + order?: number; + data: (string | Record)[]; + beginAtZero?: boolean; + fill?: string | number | boolean; backgroundColor?: string | string[]; pointBackgroundColor?: string; pointBorderColor?: string; + parsing?: { + xAxisKey?: string; + yAxisKey?: string; + }; + stack?: string; } export interface IData { @@ -24,3 +40,15 @@ export interface IData { labelsSource?: string[]; datasets: IDataset[]; } + +export interface ChartAxis { + label: string | string[]; + source: string | string[]; + type?: ChartType; + fill?: boolean; + stacked?: boolean; + stack?: string; + position: ChartAxisPosition; + min?: number; + max?: number; +} diff --git a/src/components/chart/chart.component.ts b/src/components/chart/chart.component.ts index 819f94b..9c3a351 100644 --- a/src/components/chart/chart.component.ts +++ b/src/components/chart/chart.component.ts @@ -2,13 +2,21 @@ import { AfterViewInit, Component, ElementRef, + Input, + OnChanges, OnDestroy, + SimpleChanges, ViewChild, } from '@angular/core'; -import { Chart, ChartOptions } from 'chart.js/auto'; +import { Chart } from 'chart.js/auto'; import { PluginNodata } from './plugin-nodata'; import { PluginMoreColors } from './plugin-more-colors'; -import { ChartType, IData } from './chart-type'; +import { ChartConfig } from './chart-config'; +import { ChartConfigService } from './services/chart-config.service'; +import { DatasetService } from './services/dataset.service'; +import { LabelService } from './services/label.service'; +import { OptionsService } from './services/options.service'; +import { ScaleService } from './services/scale.service'; @Component({ selector: 'fbi-chart', @@ -16,50 +24,43 @@ import { ChartType, IData } from './chart-type'; imports: [], templateUrl: './chart.component.html', styleUrl: './chart.component.scss', + providers: [ + ChartConfigService, + DatasetService, + LabelService, + OptionsService, + ScaleService, + ], }) -export class ChartComponent implements AfterViewInit, OnDestroy { +export class ChartComponent implements OnChanges, AfterViewInit, OnDestroy { @ViewChild('chart') canvas!: ElementRef; + @Input() config!: ChartConfig; + @Input() data!: Record[]; private chart: any = undefined; - private data: IData = { - labels: Array.from( - { length: 10 }, - () => `${Math.floor(Math.random() * 100)}` - ), - // labelsSource: [], - datasets: [ - { - label: 'test', - data: Array.from( - { length: 10 }, - () => `${Math.floor(Math.random() * 100)}` - ), - }, - ], - }; - constructor() {} + constructor(private chartConfigService: ChartConfigService) {} ngAfterViewInit(): void { this.initChart(); } + ngOnChanges(changes: SimpleChanges): void { + this.initChart(); + } + ngOnDestroy(): void { this.chart?.destroy?.(); } - initChart() { + private initChart() { + if (!this.canvas?.nativeElement) return; + this.chart?.destroy?.(); + const config = this.chartConfigService.chart(this.config, this.data); + console.log(config); - const opts: ChartOptions = {}; - opts.responsive = true; - opts.maintainAspectRatio = false; - - this.chart = new Chart(this.canvas.nativeElement, { - type: ChartType.Bar, - data: this.data, - options: opts, - plugins: [PluginNodata.config(), PluginMoreColors.config()] as any[], - }); + config.plugins = [PluginNodata.config(), PluginMoreColors.config()]; + this.chart = new Chart(this.canvas.nativeElement, config); } } diff --git a/src/components/chart/services/chart-config.service.ts b/src/components/chart/services/chart-config.service.ts new file mode 100644 index 0000000..14857cd --- /dev/null +++ b/src/components/chart/services/chart-config.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { ChartConfig } from '../chart-config'; +import { IData } from '../chart-type'; +import { DatasetService } from './dataset.service'; +import { LabelService } from './label.service'; +import { OptionsService } from './options.service'; + +@Injectable() +export class ChartConfigService { + constructor( + private datasetService: DatasetService, + private labelService: LabelService, + private optionsService: OptionsService + ) {} + + chart(chart: ChartConfig, data: Record[]): any { + return { + type: chart?.type, + data: this.data(chart, data), + options: this.optionsService.options(chart, data), + } as any; + } + + private data(chart: ChartConfig, data: Record[]): IData { + return { + labels: this.labelService.labels(chart, data), + datasets: this.datasetService.datasets(chart, data), + } as IData; + } +} diff --git a/src/components/chart/services/dataset.service.ts b/src/components/chart/services/dataset.service.ts new file mode 100644 index 0000000..e076972 --- /dev/null +++ b/src/components/chart/services/dataset.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { ChartConfig } from '../chart-config'; +import { ChartAxis, ChartAxisPosition, IDataset } from '../chart-type'; + +@Injectable() +export class DatasetService { + constructor() {} + + datasets(chart: ChartConfig, data: Record[]): IDataset[] { + const result: IDataset[] = []; + + (chart?.axis ?? []).forEach((axis: ChartAxis) => { + switch (axis.position) { + case ChartAxisPosition.Left: + case ChartAxisPosition.Right: + const ds = this.dataset(axis, data); + result.push(...ds); + break; + } + }); + + return result; + } + + private dataset(axis: ChartAxis, data: Record[]): IDataset[] { + let result: IDataset[] = []; + + const labels = Array.isArray(axis.label) ? axis.label : [axis.label]; + const sources = Array.isArray(axis.source) ? axis.source : [axis.source]; + + if (labels.length !== sources.length) return result; + + labels.forEach((label: string, idx: number) => { + const source = sources[idx]; + const entry: IDataset = { + label: label, + // order: idx, + data: (data ?? []).map((d: Record) => d[source]), + }; + if (axis.type) { + entry.type = axis.type; + } + if (axis.stack) { + entry.stack = axis.stack; + } + if (axis.fill) { + entry.fill = axis.fill; + } + result.push(entry); + }); + + return result; + } +} diff --git a/src/components/chart/services/label.service.ts b/src/components/chart/services/label.service.ts new file mode 100644 index 0000000..d201039 --- /dev/null +++ b/src/components/chart/services/label.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { ChartConfig } from '../chart-config'; +import { ChartAxis, ChartAxisPosition } from '../chart-type'; + +@Injectable() +export class LabelService { + constructor() {} + + labels(chart: ChartConfig, data: Record[]): string[] { + const result: string[] = []; + + (chart?.axis ?? []) + .filter((axis: ChartAxis) => + [ChartAxisPosition.Top, ChartAxisPosition.Bottom].includes( + axis.position + ) + ) + .map((axis: ChartAxis, index: number) => { + if (index > 0) return; + + const sources = Array.isArray(axis.source) + ? axis.source + : [axis.source]; + const source = sources[0]; + + (data ?? []).map((record: Record) => + result.push(record[sources[0]]) + ); + }); + + return result; + } +} diff --git a/src/components/chart/services/options.service.ts b/src/components/chart/services/options.service.ts new file mode 100644 index 0000000..8659401 --- /dev/null +++ b/src/components/chart/services/options.service.ts @@ -0,0 +1,27 @@ +import { ChartOptions } from 'chart.js'; +import { ChartConfig } from '../chart-config'; +import { ScaleService } from './scale.service'; +import { Injectable } from '@angular/core'; +import { ChartAxis } from '../chart-type'; + +@Injectable() +export class OptionsService { + constructor(private scaleService: ScaleService) {} + + options(chart: ChartConfig, data: Record[]): ChartOptions { + const result: ChartOptions = {}; + + result.responsive = true; + result.maintainAspectRatio = false; + result.scales = this.scaleService.scales(chart, data); + + const fill = + (chart?.axis ?? []).filter((axis: ChartAxis) => axis.fill).length > 0; + if (fill) { + result.plugins = result.plugins ?? {}; + result.plugins.filler = { propagate: true }; + } + + return result; + } +} diff --git a/src/components/chart/services/scale.service.ts b/src/components/chart/services/scale.service.ts new file mode 100644 index 0000000..c1e7ee8 --- /dev/null +++ b/src/components/chart/services/scale.service.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@angular/core'; +import { ChartConfig } from '../chart-config'; +import { ChartAxis, ChartAxisPosition } from '../chart-type'; + +@Injectable() +export class ScaleService { + constructor() {} + + scales(chart: ChartConfig, data: Record[]): any { + const result: any = {}; + + let x = 0; + let y = 0; + (chart?.axis ?? []).forEach((axis: ChartAxis, index: number) => { + let scale = ''; + let count = 0; + axis.position = axis.position ?? ChartAxisPosition.Bottom; + switch (axis.position) { + case ChartAxisPosition.Left: + case ChartAxisPosition.Right: + scale = 'y'; + count = y; + ++y; + break; + case ChartAxisPosition.Bottom: + case ChartAxisPosition.Top: + scale = 'x'; + count = x; + ++x; + break; + } + result[`${scale}${count === 0 ? '' : count}`] = this.getScale(axis); + }); + + return result; + } + + private getScale(item: ChartAxis): Record { + const opts: Record = {}; + + if (item.stacked) { + opts['stacked'] = true; + } + if (item.position) { + opts['position'] = item.position; + } + if (item.stack) { + opts['stack'] = item.stack; + } + if (item.min) { + opts['min'] = item.min; + } + if (item.max) { + opts['max'] = item.max; + } + + return opts; + } +} diff --git a/src/components/select/select.component.html b/src/components/select/select.component.html index 72e5e0f..80b6c7e 100644 --- a/src/components/select/select.component.html +++ b/src/components/select/select.component.html @@ -1,6 +1,8 @@ - + + diff --git a/src/components/select/select.component.ts b/src/components/select/select.component.ts index 10e1a29..2af5f9c 100644 --- a/src/components/select/select.component.ts +++ b/src/components/select/select.component.ts @@ -1,23 +1,39 @@ -import { Component, Input, inject } from '@angular/core'; +import { Component, Input, OnInit, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { Observable } from 'rxjs'; import { KeyValue } from '../../models/key-value'; import { FilterService } from '../../services/filters.service'; +import { FormsModule } from '@angular/forms'; @Component({ selector: 'fbi-select', standalone: true, - imports: [CommonModule], + imports: [CommonModule, FormsModule], templateUrl: './select.component.html', styleUrl: './select.component.scss', }) -export class SelectComponent { +export class SelectComponent implements OnInit { @Input() label!: string; @Input() key!: string; @Input() data!: Observable; + selected: any = undefined; + private filterService = inject(FilterService); + ngOnInit(): void { + this.filterService.filters$.subscribe((f: KeyValue[]) => { + let me = (f ?? []) + .filter((i: KeyValue) => i.key === this.key) + .find((_) => true); + if (me) { + this.selected = me.value; + } else { + this.selected = undefined; + } + }); + } + onChange(event: any): void { this.filterService.set(this.key, event?.target?.value); } diff --git a/src/components/table/table.component.html b/src/components/table/table.component.html index f27f42a..691a2bf 100644 --- a/src/components/table/table.component.html +++ b/src/components/table/table.component.html @@ -4,7 +4,8 @@ {{ h.label }} - + + {{ index }} {{ row[h.source] }} diff --git a/src/components/table/table.component.ts b/src/components/table/table.component.ts index 6ce3342..8ce417d 100644 --- a/src/components/table/table.component.ts +++ b/src/components/table/table.component.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { Component, Input } from '@angular/core'; import { Header } from './header'; +import { Observable } from 'rxjs'; @Component({ selector: 'fbi-table', @@ -11,5 +12,5 @@ import { Header } from './header'; }) export class TableComponent { @Input() header!: Header[]; - @Input() rows!: Record[]; + @Input() rows!: Observable[]>; } diff --git a/src/services/data.service.ts b/src/services/data.service.ts index 74309f4..ed39c6b 100644 --- a/src/services/data.service.ts +++ b/src/services/data.service.ts @@ -48,10 +48,14 @@ export class DataService { download: true, header: false, complete: (results: ParseResult>) => { - const headers = this.getHeaders((results?.data ?? []).slice(1, 3)); - const lookup = (x: string, record: Record): any => { - const idx = headers.findIndex((h: string) => x === h); - return (idx > -1 ? record[idx] : '') as any; + const h = this.getHeaders((results?.data ?? []).slice(1, 3)); + const fs = (x: string, r: Record): string => { + const idx = h.findIndex((h: string) => x === h); + return (idx > -1 ? r[idx] : '') as string; + }; + const fn = (x: string, r: Record): number => { + const idx = h.findIndex((h: string) => x === h); + return +(idx > -1 ? r[idx] : ''); }; const cohort = year + 12 - grade; this._cohorts.set(`${cohort}`, `Class of ${cohort}`); @@ -59,7 +63,7 @@ export class DataService { const data: Record[] = []; (results?.data ?? []).forEach((record: Record) => { - let code = parseInt(lookup(Csv.County_Code, record)); + let code = fn(Csv.County_Code, record); // skip non-data rows on the csv if (isNaN(code)) return; @@ -68,44 +72,39 @@ export class DataService { ms[Milestone.Year] = year; ms[Milestone.Grade] = grade; ms[Milestone.Cohort] = cohort; - ms[Milestone.County] = lookup(Csv.County_Code, record); - ms[Milestone.School] = lookup(Csv.School_Code, record); - ms[Milestone.ELACount] = lookup(Csv.ELA_Count, record); - ms[Milestone.ELAMean] = lookup(Csv.ELA_Mean, record); - ms[Milestone.ELA0] = lookup(Csv.ELA_L0, record); - ms[Milestone.ELA1] = lookup(Csv.ELA_L1, record); - ms[Milestone.ELA2] = lookup(Csv.ELA_L2, record); - ms[Milestone.ELA3] = lookup(Csv.ELA_L3, record); - ms[Milestone.MathCount] = lookup(Csv.Math_Count, record); - ms[Milestone.MathMean] = lookup(Csv.Math_Mean, record); - ms[Milestone.Math0] = lookup(Csv.Math_L0, record); - ms[Milestone.Math1] = lookup(Csv.Math_L1, record); - ms[Milestone.Math2] = lookup(Csv.Math_L2, record); - ms[Milestone.Math3] = lookup(Csv.Math_L3, record); - ms[Milestone.SciCount] = lookup(Csv.Science_Count, record); - ms[Milestone.SciMean] = lookup(Csv.Science_Mean, record); - ms[Milestone.Sci0] = lookup(Csv.Science_L0, record); - ms[Milestone.Sci1] = lookup(Csv.Science_L1, record); - ms[Milestone.Sci2] = lookup(Csv.Science_L2, record); - ms[Milestone.Sci3] = lookup(Csv.Science_L3, record); - ms[Milestone.SocCount] = lookup(Csv.Social_Count, record); - ms[Milestone.SocMean] = lookup(Csv.Social_Mean, record); - ms[Milestone.Soc0] = lookup(Csv.Social_L0, record); - ms[Milestone.Soc1] = lookup(Csv.Social_L1, record); - ms[Milestone.Soc2] = lookup(Csv.Social_L2, record); - ms[Milestone.Soc3] = lookup(Csv.Social_L3, record); + ms[Milestone.County] = fs(Csv.County_Code, record); + ms[Milestone.School] = fs(Csv.School_Code, record); + ms[Milestone.ELACount] = fn(Csv.ELA_Count, record); + ms[Milestone.ELAMean] = fn(Csv.ELA_Mean, record); + ms[Milestone.ELA0] = fn(Csv.ELA_L0, record); + ms[Milestone.ELA1] = fn(Csv.ELA_L1, record); + ms[Milestone.ELA2] = fn(Csv.ELA_L2, record); + ms[Milestone.ELA3] = fn(Csv.ELA_L3, record); + ms[Milestone.MathCount] = fn(Csv.Math_Count, record); + ms[Milestone.MathMean] = fn(Csv.Math_Mean, record); + ms[Milestone.Math0] = fn(Csv.Math_L0, record); + ms[Milestone.Math1] = fn(Csv.Math_L1, record); + ms[Milestone.Math2] = fn(Csv.Math_L2, record); + ms[Milestone.Math3] = fn(Csv.Math_L3, record); + ms[Milestone.SciCount] = fn(Csv.Science_Count, record); + ms[Milestone.SciMean] = fn(Csv.Science_Mean, record); + ms[Milestone.Sci0] = fn(Csv.Science_L0, record); + ms[Milestone.Sci1] = fn(Csv.Science_L1, record); + ms[Milestone.Sci2] = fn(Csv.Science_L2, record); + ms[Milestone.Sci3] = fn(Csv.Science_L3, record); + ms[Milestone.SocCount] = fn(Csv.Social_Count, record); + ms[Milestone.SocMean] = fn(Csv.Social_Mean, record); + ms[Milestone.Soc0] = fn(Csv.Social_L0, record); + ms[Milestone.Soc1] = fn(Csv.Social_L1, record); + ms[Milestone.Soc2] = fn(Csv.Social_L2, record); + ms[Milestone.Soc3] = fn(Csv.Social_L3, record); data.push(ms); - this._counties.set( - lookup(Csv.County_Code, record), - lookup(Csv.County_Label, record) - ); + const county = fs(Csv.County_Code, record); + this._counties.set(county, fs(Csv.County_Label, record)); this._schools.set( - `${lookup(Csv.County_Code, record)}-${lookup( - Csv.School_Code, - record - )}`, - lookup(Csv.School_Label, record) + `${county}-${fs(Csv.School_Code, record)}`, + fs(Csv.School_Label, record) ); }); diff --git a/src/services/filters.service.ts b/src/services/filters.service.ts index c6b55d3..2f1ccd1 100644 --- a/src/services/filters.service.ts +++ b/src/services/filters.service.ts @@ -22,4 +22,8 @@ export class FilterService { if (!!value) update.push(new KeyValue({ key: key, value: value })); this.filters = update; } + + reset(): void { + this.filters = []; + } }