Chart spinner

This commit is contained in:
Daniel Ponte 2025-02-22 17:36:09 -05:00
parent 5310780f05
commit 20b87abba1
5 changed files with 118 additions and 91 deletions

View file

@ -1 +1,2 @@
<div class="chart"></div> <div class="chart"></div>
<div class="spinner" *ngIf="loading"><mat-spinner></mat-spinner></div>

View file

@ -7,10 +7,7 @@ import { NgIf } from '@angular/common';
@Component({ @Component({
selector: 'chart', selector: 'chart',
imports: [ imports: [NgIf, MatProgressSpinnerModule],
NgIf,
MatProgressSpinnerModule,
],
templateUrl: './charts.component.html', templateUrl: './charts.component.html',
styleUrl: './charts.component.scss', styleUrl: './charts.component.scss',
}) })
@ -50,77 +47,87 @@ export class ChartsComponent {
} }
ngOnInit() { ngOnInit() {
this.interval.pipe(switchMap((intv) => { this.interval
this.loading = true; .pipe(
return this.callsSvc.getCallStats(intv); switchMap((intv) => {
})). d3.select(this.elementRef.nativeElement).select('.chart').html('');
subscribe((stats) => { this.loading = true;
let cMax = 0; return this.callsSvc.getCallStats(intv);
var cMin = 0; }),
let data = stats.stats.map((rec) => { )
if (cMin == 0 && rec.count > cMin) { .subscribe((stats) => {
cMin = rec.count; let cMax = 0;
} var cMin = 0;
if (rec.count < cMin) { let data = stats.stats.map((rec) => {
cMin = rec.count; if (cMin == 0 && rec.count > cMin) {
} cMin = rec.count;
if (rec.count > cMax) { }
cMax = rec.count; if (rec.count < cMin) {
} cMin = rec.count;
return { count: rec.count, time: this.dateFormat(new Date(rec.time), stats.interval) }; }
}); if (rec.count > cMax) {
// set the dimensions and margins of the graph cMax = rec.count;
var margin = { top: 30, right: 30, bottom: 70, left: 60 }, }
width = 460 - margin.left - margin.right, return {
height = 400 - margin.top - margin.bottom; count: rec.count,
// clear the old one time: this.dateFormat(new Date(rec.time), stats.interval),
d3.select(this.elementRef.nativeElement).select('.chart svg').remove(); };
const svg = d3
.select(this.elementRef.nativeElement)
.select('.chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
// X axis
var x = d3
.scaleBand()
.range([0, width])
.domain(
data.map(function (d) {
return d.time;
}),
)
.padding(0.2);
svg
.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x))
.selectAll('text')
.attr('transform', 'translate(-10,0)rotate(-45)')
.style('text-anchor', 'end');
// Add Y axis
var y = d3.scaleLinear().domain([0, cMax]).range([height, 0]);
svg.append('g').call(d3.axisLeft(y));
svg
.selectAll('mybar')
.data(data)
.enter()
.append('rect')
.attr('x', (d) => x(d.time)!)
.attr('y', function (d) {
return y(d.count);
})
.attr('width', x.bandwidth())
.attr('height', function (d) {
return height - y(d.count);
})
.attr('fill', function (d) {
return d3.interpolateTurbo((d.count - cMin) / (cMax - cMin));
}); });
this.loading = false; // set the dimensions and margins of the graph
}); var margin = { top: 30, right: 30, bottom: 70, left: 60 },
width = 460 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// clear the old one
d3.select(this.elementRef.nativeElement).select('.chart').html('');
const svg = d3
.select(this.elementRef.nativeElement)
.select('.chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr(
'transform',
'translate(' + margin.left + ',' + margin.top + ')',
);
// X axis
var x = d3
.scaleBand()
.range([0, width])
.domain(
data.map(function (d) {
return d.time;
}),
)
.padding(0.2);
svg
.append('g')
.attr('transform', 'translate(0,' + height + ')')
.call(d3.axisBottom(x))
.selectAll('text')
.attr('transform', 'translate(-10,0)rotate(-45)')
.style('text-anchor', 'end');
// Add Y axis
var y = d3.scaleLinear().domain([0, cMax]).range([height, 0]);
svg.append('g').call(d3.axisLeft(y));
svg
.selectAll('mybar')
.data(data)
.enter()
.append('rect')
.attr('x', (d) => x(d.time)!)
.attr('y', function (d) {
return y(d.count);
})
.attr('width', x.bandwidth())
.attr('height', function (d) {
return height - y(d.count);
})
.attr('fill', function (d) {
return d3.interpolateTurbo((d.count - cMin) / (cMax - cMin));
});
this.loading = false;
});
} }
} }

View file

@ -1,14 +1,14 @@
<mat-card class="chart" appearance="outlined"> <mat-card class="chart" appearance="outlined">
<div class="chartTitle">Calls By</div>
<form [formGroup]="form"> <form [formGroup]="form">
<mat-form-field> <mat-form-field>
<mat-select formControlName="callsPer"> <label>Calls By</label>
<mat-select formControlName="callsPer">
<mat-option value="hour">Hour</mat-option> <mat-option value="hour">Hour</mat-option>
<mat-option value="day">Day</mat-option> <mat-option value="day">Day</mat-option>
<mat-option value="week">Week</mat-option> <mat-option value="week">Week</mat-option>
<mat-option value="month">Month</mat-option> <mat-option value="month">Month</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</form> </form>
<chart [interval]="callsOb"></chart> <chart #chart [interval]="callsOb"></chart>
</mat-card> </mat-card>

View file

@ -1,6 +1,6 @@
mat-card.chart { mat-card.chart {
width: 500px; width: 500px;
height: 400px; height: 500px;
margin: 30px 30px 40px 40px; margin: 30px 30px 40px 40px;
} }

View file

@ -1,15 +1,35 @@
import { Component, computed, signal, ViewChild } from '@angular/core'; import { Component, computed, signal, ViewChild } from '@angular/core';
import { ChartsComponent } from '../charts/charts.component'; import { ChartsComponent } from '../charts/charts.component';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; import {
import { debounceTime, filter, Observable, startWith, switchAll, switchMap } from 'rxjs'; FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
} from '@angular/forms';
import {
debounceTime,
filter,
Observable,
startWith,
switchAll,
switchMap,
} from 'rxjs';
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
@Component({ @Component({
selector: 'app-home', selector: 'app-home',
imports: [ChartsComponent, MatCardModule, MatFormFieldModule, MatSelectModule, MatInputModule, FormsModule, ReactiveFormsModule ], imports: [
ChartsComponent,
MatCardModule,
MatFormFieldModule,
MatSelectModule,
MatInputModule,
FormsModule,
ReactiveFormsModule,
],
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrl: './home.component.scss', styleUrl: './home.component.scss',
}) })
@ -20,12 +40,11 @@ export class HomeComponent {
callsOb!: Observable<string>; callsOb!: Observable<string>;
ngOnInit() { ngOnInit() {
this.form.controls["callsPer"].setValue("week"); this.form.controls['callsPer'].setValue('week');
this.callsOb = this.form.controls['callsPer'].valueChanges this.callsOb = this.form.controls['callsPer'].valueChanges.pipe(
.pipe( startWith('week'),
startWith("week"),
debounceTime(300), debounceTime(300),
filter(val => val !== null) filter((val) => val !== null),
); );
} }
} }