Added a bunch of stuff

This commit is contained in:
jfusia
2024-05-22 14:07:00 -04:00
parent 547783aa73
commit 17f5b675b9
70 changed files with 45689 additions and 365 deletions

View File

@@ -0,0 +1,25 @@
export enum ChartType {
Bar = 'bar',
Line = 'line',
Scatter = 'scatter',
Bubble = 'bubble',
Pie = 'pie',
Doughnut = 'doughnut',
PolarArea = 'polarArea',
Radar = 'radar',
}
export interface IDataset {
type?: ChartType;
borderColor?: string;
data: string[];
backgroundColor?: string | string[];
pointBackgroundColor?: string;
pointBorderColor?: string;
}
export interface IData {
labels?: string[];
labelsSource: string[];
datasets: IDataset[];
}

View File

@@ -0,0 +1 @@
<canvas #chart></canvas>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChartComponent } from './chart.component';
describe('ChartComponent', () => {
let component: ChartComponent;
let fixture: ComponentFixture<ChartComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ChartComponent]
})
.compileComponents();
fixture = TestBed.createComponent(ChartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,54 @@
import {
AfterViewInit,
Component,
ElementRef,
OnDestroy,
ViewChild,
} from '@angular/core';
import { Chart, ChartOptions } from 'chart.js';
import { PluginNodata } from './plugin-nodata';
import { PluginMoreColors } from './plugin-more-colors';
import { ChartType, IData } from './chart-type';
@Component({
selector: 'fbi-chart',
standalone: true,
imports: [],
templateUrl: './chart.component.html',
styleUrl: './chart.component.scss',
})
export class ChartComponent implements AfterViewInit, OnDestroy {
@ViewChild('chart') canvas!: ElementRef;
private chart: any = undefined;
private data: IData = {
labels: [],
labelsSource: [],
datasets: [],
};
constructor() {}
ngAfterViewInit(): void {
this.initChart();
}
ngOnDestroy(): void {
this.chart?.destroy?.();
}
initChart() {
this.chart?.destroy?.();
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[],
});
}
}

View File

@@ -0,0 +1,73 @@
import { ChartType, IDataset } from './chart-type';
interface IOptions {
colors: string[];
fills: string[];
fillAlpha: number;
}
interface IChart {
config: { type: ChartType };
ctx: CanvasRenderingContext2D;
data: { datasets: IDataset[] };
}
export class PluginMoreColors {
static config() {
return {
id: 'morecolors',
beforeUpdate: function (chart: IChart, args: any, options: IOptions) {
const { datasets } = chart?.data ?? { datasets: [] };
const { colors, fills, fillAlpha } = options;
const c_length = colors?.length ?? 0;
const f_length = fills?.length ?? 0;
if (c_length === 0) return;
datasets.forEach((dataset: IDataset, index: number) => {
const color = colors[index % c_length];
const fill = f_length > 0 ? fills[index % f_length] : undefined;
switch (dataset.type ?? chart?.config?.type) {
case ChartType.Line:
case ChartType.Radar:
case ChartType.Scatter:
// line connecting points
dataset.borderColor = dataset.borderColor ?? color;
if (fill)
dataset.backgroundColor = dataset.backgroundColor ?? fill;
// points
dataset.pointBorderColor = dataset.pointBorderColor ?? color;
if (fill)
dataset.pointBackgroundColor =
dataset.pointBackgroundColor ?? fill;
break;
case ChartType.Doughnut:
case ChartType.Pie:
case ChartType.PolarArea:
const current = dataset.backgroundColor ?? [];
dataset.backgroundColor = (dataset.data ?? []).map(
(_, index: number) => current[index] ?? colors[index % length]
);
break;
case ChartType.Bar:
dataset.borderColor = dataset.borderColor ?? color;
if (fill)
dataset.backgroundColor = dataset.backgroundColor ?? fill;
break;
default:
dataset.borderColor = dataset.borderColor ?? color;
if (fill)
dataset.backgroundColor = dataset.backgroundColor ?? fill;
break;
}
});
},
defaults: {
colors: [],
fills: [],
fillAlpha: 1.0,
} as IOptions,
};
}
}

View File

@@ -0,0 +1,75 @@
import { ChartType, IDataset } from './chart-type';
interface IOptions {
type: ChartType;
color: string;
innerRadius: number;
radius: string | number;
}
interface IArea {
top: number;
right: number;
bottom: number;
left: number;
}
interface IChart {
chartArea: IArea;
config: { type: ChartType };
ctx: CanvasRenderingContext2D;
data: { datasets: IDataset[] };
}
export class PluginNodata {
static config() {
const calculate = (radius: number, config: string | number): number => {
if (typeof config === 'string' && config.indexOf('%') > -1) {
const value = +config.replace('%', '') / 100;
return value * radius;
} else {
return config as number;
}
};
return {
id: 'nodata',
afterDraw(chart: IChart, args: any, options: IOptions) {
const { datasets } = chart.data ?? { datasets: [] };
const { type, color, innerRadius, radius } = options;
const hasData: boolean = datasets
.map((ds: IDataset) => ds.data?.length > 0)
.reduce((acc: boolean, cur: boolean) => acc || cur, false);
if (!hasData) {
const {
chartArea: { left, top, right, bottom },
ctx,
} = chart;
if (type === ChartType.Pie) {
const centerX = (left + right) / 2;
const centerY = (top + bottom) / 2;
const r = Math.min(right - left, bottom - top) / 2;
const ir = calculate(r, innerRadius);
const or = calculate(r, radius);
const width = or - ir;
ctx.beginPath();
ctx.lineWidth = width;
ctx.strokeStyle = color ?? 'rgba(250, 250, 250, 0.5)';
ctx.arc(centerX, centerY, or - width / 2, 0, 2 * Math.PI);
ctx.stroke();
}
}
},
defaults: {
type: ChartType.Pie,
color: 'rgba(250, 250, 250, 0.5)',
innerRadius: 0,
radius: '100%',
} as IOptions,
};
}
}

View File

@@ -0,0 +1,4 @@
<label for="select">{{ label }}</label>
<select name="select">
<option *ngFor="let item of data" [value]="item">{{ item }}</option>
</select>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SelectComponent } from './select.component';
describe('SelectComponent', () => {
let component: SelectComponent;
let fixture: ComponentFixture<SelectComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SelectComponent]
})
.compileComponents();
fixture = TestBed.createComponent(SelectComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,17 @@
import { Component, Input, inject } from '@angular/core';
import { DataService } from '../../services/data.service';
import { CommonModule } from '@angular/common';
@Component({
selector: 'fbi-select',
standalone: true,
imports: [CommonModule],
templateUrl: './select.component.html',
styleUrl: './select.component.scss',
})
export class SelectComponent {
@Input() label!: string;
@Input() data!: (string | number)[];
private dataService = inject(DataService);
}