Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
395 changes: 0 additions & 395 deletions .betterer.results

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Chart, ChartConfiguration, ChartDataset, Color, LegendItem, LineControl
import 'chartjs-adapter-date-fns';
import { IWidget } from '../../interfaces/widgets-interface';
import { IKipSeriesDefinition } from '../../services/kip-series-api-client.service';
import { isKipTemplateSeriesDefinition, type IKipTemplateSeriesDefinition } from '../../contracts/kip-series-contract';
import { isKipTemplateSeriesDefinition, type IKipConcreteSeriesDefinition, type IKipTemplateSeriesDefinition } from '../../contracts/kip-series-contract';
import {
describeElectricalDualAxisSeries,
getAxisConfigsForWidget,
Expand All @@ -19,7 +19,7 @@ import {
transformDualAxisMetricValue
} from '../../contracts/electrical-history-chart.contract';
import { HistoryToChartMapperService } from '../../services/history-to-chart-mapper.service';
import { AppService } from '../../services/app-service';
import { AppService, ITheme } from '../../services/app-service';
import { UnitsService } from '../../services/units.service';
import { HistoryApiClientService } from '../../services/history-api-client.service';
import { MatIconModule } from '@angular/material/icon';
Expand All @@ -30,7 +30,8 @@ Chart.register(LineController, LineElement, LinearScale, PointElement, TimeScale

interface ChartPoint {
x: number;
y: number;
/** A gap in the source history (no value at this timestamp) is a normal, chart.js-renderable state. */
y: number | null;
}
type HistoryChartDataset = ChartDataset<'line', ChartPoint[]>;

Expand Down Expand Up @@ -214,7 +215,7 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,

// Apply conversion based on dual-axis metric metadata first, then optional widget-level override.
chartPoints = datapoints.map(point => {
let y = point.data.value as number;
let y: number | null = point.data.value;
if (typeof y === 'number' && Number.isFinite(y)) {
if (dualAxisSeries) {
y = transformDualAxisMetricValue(dualAxisSeries, y);
Expand Down Expand Up @@ -279,17 +280,20 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,
}

private getSeriesPalette(): string[] {
const theme = this.theme();
if (!theme) return [];

const configColorKey = this.data.widget?.config?.color || 'blue';
const preferredColor = this.resolveThemeColor(configColorKey);
let palette = [
this.theme().green,
this.theme().orange,
this.theme().pink,
this.theme().purple,
this.theme().yellow,
this.theme().grey,
this.theme().blue,
this.theme().contrast
theme.green,
theme.orange,
theme.pink,
theme.purple,
theme.yellow,
theme.grey,
theme.blue,
theme.contrast
];

const existingIndex = palette.findIndex(color => color?.toLowerCase() === preferredColor.toLowerCase());
Expand All @@ -303,7 +307,9 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,
}

private resolveThemeColor(colorKey: string): string {
const themeColor = this.theme()[colorKey as keyof ReturnType<typeof this.theme>];
const theme = this.theme();
if (!theme) return colorKey;
const themeColor = theme[colorKey as keyof ITheme];
return typeof themeColor === 'string' && themeColor.length > 0 ? themeColor : colorKey;
}

Expand Down Expand Up @@ -416,14 +422,16 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,
return [series];
}

return matchedPaths.map((path, index) => ({
return matchedPaths.map((path, index): IKipConcreteSeriesDefinition => ({
...series,
seriesId: `${series.seriesId}:resolved:${index}`,
datasetUuid: `${series.datasetUuid}:resolved:${index}`,
path,
// A resolved concrete path is no longer a template: clear all template-only bookkeeping fields.
expansionMode: null,
familyKey: null,
allowedIds: null
allowedIds: null,
trackedDevices: null
}));
}

Expand Down Expand Up @@ -521,6 +529,11 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,
return;
}

const theme = this.theme();
if (!theme) {
return;
}

this.chart?.destroy();

const unitLabel = this.getPrimaryAxisUnitLabel();
Expand Down Expand Up @@ -558,18 +571,18 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,

},
ticks: {
color: this.theme().contrastDim,
color: theme.contrastDim,
},
grid: {
color: this.theme().contrastDimmer
color: theme.contrastDimmer
}
},
...this.buildYScales(unitLabel)
},
plugins: {
legend: {
labels: {
color: this.theme().contrastDim,
color: theme.contrastDim,
generateLabels: chart => this.buildLegendLabels(chart)
}
},
Expand All @@ -588,7 +601,7 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,
: undefined;

return {
borderColor: (legendItem?.strokeStyle ?? this.theme().contrast) as string,
borderColor: (legendItem?.strokeStyle ?? theme.contrast) as string,
backgroundColor: 'rgba(0, 0, 0, 0)',
borderWidth: 2,
borderRadius: 0,
Expand Down Expand Up @@ -636,20 +649,23 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,
}

private buildYScales(unitLabel: string): NonNullable<NonNullable<ChartConfiguration<'line'>['options']>['scales']> {
const theme = this.theme();
if (!theme) return {};

const dualAxisWidgetType = this.resolveDualAxisWidgetType();
if (!dualAxisWidgetType) {
return {
y: {
ticks: {
color: this.theme().contrastDim
color: theme.contrastDim
},
grid: {
color: this.theme().contrastDimmer
color: theme.contrastDimmer
},
title: {
display: !!unitLabel,
text: unitLabel ? `${unitLabel}` : '',
color: this.theme().contrastDim
color: theme.contrastDim
}
}
};
Expand All @@ -662,19 +678,19 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,
const axisMetric = this.resolvePrimaryMetricForAxis(dualAxisWidgetType, axisConfig.axisId);
const axisColor = axisMetric
? this.getDualAxisMetricColor(dualAxisWidgetType, axisMetric)
: this.theme().contrastDim;
: theme.contrastDim;

scales[axisConfig.axisId] = {
type: 'linear',
position: axisConfig.position,
...(axisConfig.min != null ? { min: axisConfig.min } : {}),
...(axisConfig.max != null ? { max: axisConfig.max } : {}),
ticks: {
color: this.theme().contrastDim,
color: theme.contrastDim,
callback: (value: unknown) => this.formatAxisTick(value, axisConfig.tickSuffix)
},
grid: {
color: this.theme().contrastDimmer,
color: theme.contrastDimmer,
...(axisConfig.drawOnChartArea === false ? { drawOnChartArea: false } : {})
},
title: {
Expand Down Expand Up @@ -707,7 +723,9 @@ export class WidgetHistoryChartDialogComponent implements OnInit, AfterViewInit,
const dataset = label.datasetIndex != null
? chart.data.datasets[label.datasetIndex] as HistoryChartDataset
: null;
const borderDash = Array.isArray(dataset?.borderDash) ? dataset.borderDash : [];
const borderDash: number[] = Array.isArray(dataset?.borderDash)
? dataset.borderDash.filter((v): v is number => typeof v === 'number')
: [];
return {
...label,
fillStyle: 'transparent' as Color,
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/interfaces/widgets-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ export interface IAutopilotConfig {
/** Set heading direction type is Magnetic or True */
headingDirectionTrue: boolean,
/** Set API version of the autopilot. v1 only supports Signal K autopilot plugin (also known as Raymarine autopilot) */
apiVersion: "v1" | "v2",
apiVersion: "v1" | "v2" | null,
/** The autopilot provider supports multiple autopilot
* operating simultaneously. Set the autopilot instance ID to control
* ('_default', 'pypilot-1', 'pypilot-2', 'raymarine-1', etc.). Use
Expand Down
83 changes: 45 additions & 38 deletions src/app/widgets/minichart/minichart.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ import ChartStreaming from '@aziham/chartjs-plugin-streaming';
Chart.register(ChartStreaming, TimeScale, LinearScale, LineController, PointElement, LineElement, Filler, CategoryScale);

interface IChartColors {
valueLine: string,
valueFill: string,
averageLine: string,
averageFill: string,
averageChartLine: string,
chartLabel: string,
chartValue: string
valueLine: string | undefined,
valueFill: string | undefined,
averageLine: string | undefined,
averageFill: string | undefined,
averageChartLine: string | undefined,
chartLabel: string | undefined,
chartValue: string | undefined
}
interface IDataSetRow {
x: number,
y: number
x: number | null,
y: number | null
}

/** The numeric widget's background sparkline always shows the same short fixed window. */
Expand All @@ -41,13 +41,14 @@ const MINICHART_PERIOD = 0.2;
})
export class MinichartComponent implements OnDestroy {
protected readonly theme = input.required<ITheme>();
public color: string = null;
public dataPath: string = null;
public dataSource: string = null;
public convertUnitTo: string = null;
public numDecimal: number = null;
public yScaleMin: number = null;
public yScaleMax: number = null;
public color = '';
public dataPath: string | null = null;
public dataSource = '';
/** No unit conversion configured is a normal state (parent passes this through unfallbacked). */
public convertUnitTo: string | null | undefined = null;
public numDecimal = 0;
public yScaleMin = 0;
public yScaleMax = 0;
public inverseYAxis = false;
public verticalChart: boolean | null = null;
protected unitsService = inject(UnitsService);
Expand All @@ -58,7 +59,7 @@ export class MinichartComponent implements OnDestroy {
public lineChartData: ChartData<'line', { x: number, y: number }[]> = {
datasets: []
};
public lineChartOptions: ChartConfiguration['options'] = {
public lineChartOptions: NonNullable<ChartConfiguration['options']> = {
parsing: false,
datasets: {
line: {
Expand All @@ -75,8 +76,8 @@ export class MinichartComponent implements OnDestroy {
}
public lineChartType: ChartType = 'line';
private chart;
private streamSub: Subscription = null;
private dataSourceInfo: IChartDataSourceInfo = null;
private streamSub: Subscription | null = null;
private dataSourceInfo: IChartDataSourceInfo | null = null;
private isDestroyed = false;
private lastChartSignature: string | null = null;

Expand Down Expand Up @@ -150,6 +151,9 @@ export class MinichartComponent implements OnDestroy {
}

private setChartOptions() {
const dataSourceInfo = this.dataSourceInfo;
if (!dataSourceInfo) return;

this.lineChartOptions.maintainAspectRatio = false;
this.lineChartOptions.animation = false;

Expand All @@ -160,10 +164,10 @@ export class MinichartComponent implements OnDestroy {
x: {
display: false,
position: "right",
suggestedMin: this.config.enableMinMaxScaleLimit ? null : this.yScaleMin,
suggestedMax: this.config.enableMinMaxScaleLimit ? null : this.yScaleMax,
min: this.config.enableMinMaxScaleLimit ? this.yScaleMin : null,
max: this.config.enableMinMaxScaleLimit ? this.yScaleMax : null,
suggestedMin: this.config.enableMinMaxScaleLimit ? undefined : this.yScaleMin,
suggestedMax: this.config.enableMinMaxScaleLimit ? undefined : this.yScaleMax,
min: this.config.enableMinMaxScaleLimit ? this.yScaleMin : undefined,
max: this.config.enableMinMaxScaleLimit ? this.yScaleMax : undefined,
beginAtZero: this.config.startScaleAtZero,
reverse: this.inverseYAxis,
title: {
Expand Down Expand Up @@ -252,10 +256,10 @@ export class MinichartComponent implements OnDestroy {
y: {
display: false,
position: "right",
suggestedMin: this.config.enableMinMaxScaleLimit ? null : this.yScaleMin,
suggestedMax: this.config.enableMinMaxScaleLimit ? null : this.yScaleMax,
min: this.config.enableMinMaxScaleLimit ? this.yScaleMin : null,
max: this.config.enableMinMaxScaleLimit ? this.yScaleMax : null,
suggestedMin: this.config.enableMinMaxScaleLimit ? undefined : this.yScaleMin,
suggestedMax: this.config.enableMinMaxScaleLimit ? undefined : this.yScaleMax,
min: this.config.enableMinMaxScaleLimit ? this.yScaleMin : undefined,
max: this.config.enableMinMaxScaleLimit ? this.yScaleMax : undefined,
beginAtZero: this.config.startScaleAtZero,
reverse: this.inverseYAxis,
title: {
Expand Down Expand Up @@ -284,8 +288,8 @@ export class MinichartComponent implements OnDestroy {
display: false
},
streaming: {
duration: this.dataSourceInfo.maxDataPoints * this.dataSourceInfo.sampleTime,
delay: this.dataSourceInfo.sampleTime,
duration: dataSourceInfo.maxDataPoints * dataSourceInfo.sampleTime,
delay: dataSourceInfo.sampleTime,
frameRate: MINICHART_TIME_SCALE === "day" ? 5 : MINICHART_TIME_SCALE === "hour" ? 8 : MINICHART_TIME_SCALE === "minute" ? 15 : 30,
}
}
Expand Down Expand Up @@ -337,13 +341,13 @@ export class MinichartComponent implements OnDestroy {
private getThemeColors(): IChartColors {
const widgetColor = this.color;
const colors: IChartColors = {
valueLine: null,
valueFill: null,
averageLine: null,
averageFill: null,
averageChartLine: null,
chartLabel: null,
chartValue: null
valueLine: undefined,
valueFill: undefined,
averageLine: undefined,
averageFill: undefined,
averageChartLine: undefined,
chartLabel: undefined,
chartValue: undefined
};

switch (widgetColor) {
Expand Down Expand Up @@ -498,8 +502,11 @@ export class MinichartComponent implements OnDestroy {
this.streamSub?.unsubscribe();

const info = this.dataSourceInfo;
const dataPath = this.dataPath;
if (!info || !dataPath) return;

const params: IHistoryChartStreamParams = {
path: this.dataPath,
path: dataPath,
source: this.dataSource ?? 'default',
windowMs: resolveWindowMs(MINICHART_TIME_SCALE, MINICHART_PERIOD),
sampleTime: info.sampleTime,
Expand Down Expand Up @@ -552,7 +559,7 @@ export class MinichartComponent implements OnDestroy {

private transformDatasetRows(rows: IDatasetServiceDatapoint[], datasetType): IDataSetRow[] {
const convert = (v: number) =>
this.unitsService.convertToUnit(this.convertUnitTo, v);
this.unitsService.convertToUnit(this.convertUnitTo ?? '', v);
const verticalChart = this.verticalChart;
const avgKey = this.config.datasetAverageArray;

Expand Down
Loading
Loading