128 lines
4.4 KiB
TypeScript
128 lines
4.4 KiB
TypeScript
export interface TrendDataPoint {
|
|
day: string;
|
|
value: number;
|
|
}
|
|
|
|
export interface ChartConfig {
|
|
chartData: any;
|
|
chartOptions: any;
|
|
}
|
|
|
|
interface DatasetOverrides {
|
|
label: string;
|
|
backgroundColor: string | string[];
|
|
datalabels?: object;
|
|
}
|
|
|
|
interface YAxisTicks {
|
|
min: number;
|
|
max: number;
|
|
stepSize: number;
|
|
}
|
|
|
|
export class ChartBuilderUtils {
|
|
private static readonly GREEN_DARK = '#2E7D32';
|
|
private static readonly GRID_LINE = '#F0F0F0';
|
|
private static readonly AXIS_LABEL = '#3a4450';
|
|
private static readonly DATA_LABEL = '#222222';
|
|
|
|
private static readonly SHARED_X_AXIS = [
|
|
{ gridLines: { display: false }, ticks: { fontColor: ChartBuilderUtils.AXIS_LABEL } }
|
|
];
|
|
|
|
private static buildYAxis(ticks: YAxisTicks) {
|
|
return [{ gridLines: { color: ChartBuilderUtils.GRID_LINE }, ticks: { fontColor: ChartBuilderUtils.AXIS_LABEL, beginAtZero: true, ...ticks } }];
|
|
}
|
|
|
|
/**
|
|
* Computes a nice Y-axis scale from the actual data values.
|
|
* `preferredMax` acts as both the zero-data fallback and a minimum floor —
|
|
* the computed max will never be less than `preferredMax`.
|
|
*/
|
|
private static niceYTicks(values: number[], preferredMax: number): YAxisTicks {
|
|
const dataMax = values.length > 0 ? Math.max(...values) : 0;
|
|
// Use whichever is larger: data-driven headroom or the preferred floor
|
|
const raw = Math.max(dataMax * 1.2, preferredMax);
|
|
const magnitude = Math.pow(10, Math.floor(Math.log10(raw / 5)));
|
|
const stepSize = Math.ceil((raw / 5) / magnitude) * magnitude;
|
|
const max = stepSize * 5;
|
|
return { min: 0, max, stepSize };
|
|
}
|
|
|
|
private static buildBaseChartConfig(
|
|
data: TrendDataPoint[],
|
|
dataset: DatasetOverrides,
|
|
yAxisTicks: YAxisTicks,
|
|
tooltipUnit: string,
|
|
plugins?: object
|
|
): ChartConfig {
|
|
const values = data.map(d => d.value);
|
|
return {
|
|
chartData: {
|
|
labels: data.map(d => d.day),
|
|
datasets: [{
|
|
label: dataset.label,
|
|
data: values,
|
|
fill: false,
|
|
borderColor: ChartBuilderUtils.GREEN_DARK,
|
|
backgroundColor: dataset.backgroundColor,
|
|
pointBackgroundColor: ChartBuilderUtils.GREEN_DARK,
|
|
pointRadius: 4,
|
|
tension: 0.3,
|
|
...(dataset.datalabels ? { datalabels: dataset.datalabels } : {})
|
|
}]
|
|
},
|
|
chartOptions: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
legend: { display: false },
|
|
...(plugins ? { plugins } : {}),
|
|
scales: {
|
|
xAxes: ChartBuilderUtils.SHARED_X_AXIS,
|
|
yAxes: ChartBuilderUtils.buildYAxis(yAxisTicks)
|
|
},
|
|
tooltips: {
|
|
callbacks: { label: (item: any) => ` ${item.yLabel} ${tooltipUnit}` }
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private static interpolateGreenByValue(values: number[]): string[] {
|
|
const light = { r: 165, g: 214, b: 167 };
|
|
const dark = { r: 46, g: 125, b: 50 };
|
|
const min = Math.min(...values);
|
|
const max = Math.max(...values);
|
|
return values.map(val => {
|
|
const t = (max === min) ? 0 : (val - min) / (max - min);
|
|
return `rgb(${Math.round(light.r + (dark.r - light.r) * t)},${Math.round(light.g + (dark.g - light.g) * t)},${Math.round(light.b + (dark.b - light.b) * t)})`;
|
|
});
|
|
}
|
|
|
|
static hoursChart(data: TrendDataPoint[]): ChartConfig {
|
|
const values = data.map(d => d.value);
|
|
return ChartBuilderUtils.buildBaseChartConfig(
|
|
data,
|
|
{ label: $localize`:Hours flown chart dataset label@@hoursChartLabel:Hours Flown`, backgroundColor: ChartBuilderUtils.GREEN_DARK },
|
|
ChartBuilderUtils.niceYTicks(values, 5),
|
|
$localize`:Hours unit abbreviation@@unitHrs:hrs`
|
|
);
|
|
}
|
|
|
|
static hectaresChart(data: TrendDataPoint[], areaUnit = 'ha'): ChartConfig {
|
|
const values = data.map(d => d.value);
|
|
const preferredMax = areaUnit === 'ac' ? 2000 : 500;
|
|
const datalabels = { anchor: 'end', align: 'end', color: ChartBuilderUtils.DATA_LABEL, font: { weight: 'bold' }, formatter: (v: number) => v };
|
|
const datasetLabel = areaUnit === 'ac'
|
|
? $localize`:Acres sprayed chart dataset label@@acresChartLabel:Acres Sprayed`
|
|
: $localize`:Hectares sprayed chart dataset label@@hectaresChartLabel:Hectares Sprayed`;
|
|
return ChartBuilderUtils.buildBaseChartConfig(
|
|
data,
|
|
{ label: datasetLabel, backgroundColor: ChartBuilderUtils.interpolateGreenByValue(values), datalabels },
|
|
ChartBuilderUtils.niceYTicks(values, preferredMax),
|
|
areaUnit,
|
|
{ datalabels }
|
|
);
|
|
}
|
|
}
|