'~/(.*)$': '<rootDir>/src/$1',
'^@carbon/icons/es/(.*)$': '@carbon/icons/lib/$1.js',
'^lodash-es$': 'lodash',
+ '^@carbon/charts$': '<rootDir>/node_modules/@carbon/charts/dist/index.mjs'
},
moduleFileExtensions: ['ts', 'html', 'js', 'json', 'mjs', 'cjs'],
preset: 'jest-preset-angular',
let component: AreaChartComponent;
let fixture: ComponentFixture<AreaChartComponent>;
let numberFormatterService: NumberFormatterService;
- let datePipe: DatePipe;
const mockData: ChartPoint[] = [
{
unitlessLabels: ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
};
- const datePipeMock = {
- transform: jest.fn().mockReturnValue('01 Jan, 00:00:00')
- };
-
await TestBed.configureTestingModule({
imports: [ChartsModule, AreaChartComponent],
- providers: [
- { provide: NumberFormatterService, useValue: numberFormatterMock },
- { provide: DatePipe, useValue: datePipeMock }
- ],
+ providers: [DatePipe, { provide: NumberFormatterService, useValue: numberFormatterMock }],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
fixture = TestBed.createComponent(AreaChartComponent);
component = fixture.componentInstance;
numberFormatterService = TestBed.inject(NumberFormatterService);
- datePipe = TestBed.inject(DatePipe);
});
it('should mount', () => {
expect(component.chartOptions?.tooltip?.enabled).toBe(true);
});
- it('should format tooltip with custom date format', () => {
- const testDate = new Date('2024-01-01T12:30:45Z');
- const formattedDate = '01 Jan, 12:30:45';
- const defaultHTML = '<div><p class="value">2024-01-01T12:30:45Z</p></div>';
+ it('should wire tooltip customHTML with arity 2', () => {
+ component.chartTitle = 'Test Chart';
+ component.dataUnit = 'B/s';
+ component.rawData = mockData;
+
+ (numberFormatterService.formatFromTo as jest.Mock).mockReturnValue('4.00 KiB/s');
+
+ fixture.detectChanges();
+ component.ngOnChanges({
+ rawData: new SimpleChange(null, mockData, false)
+ });
+
+ expect(component.chartOptions?.tooltip?.customHTML?.length).toBe(2);
+ });
- (datePipe.transform as jest.Mock).mockReturnValue(formattedDate);
+ it('should replace x-value label with Time in tooltip HTML', () => {
+ const defaultHTML =
+ '<ul class="multi-tooltip"><li><div class="datapoint-tooltip">' +
+ '<div class="label"><p>x-value</p></div><p class="value">ignored</p></div></li></ul>';
- const result = component.formatChartTooltip(defaultHTML, [{ date: testDate }]);
+ const result = component.formatChartTooltip(defaultHTML);
- expect(datePipe.transform).toHaveBeenCalledWith(testDate, 'dd MMM, HH:mm:ss');
- expect(result).toContain(formattedDate);
- expect(result).not.toContain('2024-01-01T12:30:45Z');
+ expect(result).toContain('<p>Time</p>');
+ expect(result).not.toContain('x-value');
+ expect(result).toContain('<p class="value">ignored</p>');
});
- it('should return default HTML if tooltip data is empty', () => {
+ it('should return default HTML if tooltip has no x-value label', () => {
const defaultHTML = '<div><p>Default</p></div>';
- const result = component.formatChartTooltip(defaultHTML, []);
+ const result = component.formatChartTooltip(defaultHTML);
expect(result).toBe(defaultHTML);
});
ChartTabularData,
ToolbarControlTypes,
ScaleTypes,
- ChartsModule
+ ChartsModule,
+ TickRotations
} from '@carbon/charts-angular';
import merge from 'lodash.merge';
import { NumberFormatterService } from '../../services/number-formatter.service';
-import { DatePipe } from '@angular/common';
import { ChartPoint } from '../../models/area-chart-point';
import {
DECIMAL,
getDivisor,
getLabels
} from '../../helpers/unit-format-utils';
+import { DatePipe } from '@angular/common';
+
+const DEFAULT_TICKS_COUNT = 4;
+const FIVE_MINUTE_SPAN_SECONDS = 300;
+const TIME_SPAN_TOLERANCE_SECONDS = 30;
+const TOOLTIP_TIME_FORMAT = 'MMM d, hh:mm a';
@Component({
selector: 'cd-area-chart',
private cdr = inject(ChangeDetectorRef);
private numberFormatter: NumberFormatterService = inject(NumberFormatterService);
- private datePipe = inject(DatePipe);
private lastEmittedRawValues?: Record<string, number>;
+ private datePipe = inject(DatePipe);
+
ngOnChanges(changes: SimpleChanges): void {
if (changes['rawData'] && this.rawData?.length) {
this.updateChart();
this.lastEmittedRawValues = { ...latestEntry.values };
}
+ // Calculate time span in seconds from raw data timestamps
+ private getTimeSpanInSeconds(): number {
+ if (!this.rawData || this.rawData.length < 2) {
+ return 0;
+ }
+ const firstTimestamp = new Date(this.rawData[0].timestamp).getTime();
+ const lastTimestamp = new Date(this.rawData[this.rawData.length - 1].timestamp).getTime();
+ return (lastTimestamp - firstTimestamp) / 1000;
+ }
+
private getChartOptions(max: number, labels: string[], divisor: number): AreaChartOptions {
+ const timeSpan = this.getTimeSpanInSeconds();
+ const isFiveMinuteSpan =
+ timeSpan > 0 &&
+ timeSpan >= FIVE_MINUTE_SPAN_SECONDS - TIME_SPAN_TOLERANCE_SECONDS &&
+ timeSpan <= FIVE_MINUTE_SPAN_SECONDS + TIME_SPAN_TOLERANCE_SECONDS;
return {
legend: {
enabled: this.legendEnabled
mapsTo: 'date',
scaleType: ScaleTypes.TIME,
ticks: {
- number: 4,
- rotateIfSmallerThan: 0
+ number: DEFAULT_TICKS_COUNT,
+ rotation: isFiveMinuteSpan ? TickRotations.ALWAYS : TickRotations.AUTO
}
},
left: {
tooltip: {
enabled: true,
showTotal: false,
- valueFormatter: (value: number): string =>
- (this.formatValueForChart(value, labels, divisor, this.decimals) || value).toString(),
- customHTML: (data, defaultHTML) => this.formatChartTooltip(defaultHTML, data)
+ truncation: {
+ type: 'none'
+ },
+ valueFormatter: (value: number | Date, label: string): string => {
+ if (value instanceof Date && label === 'x-value') {
+ return this.datePipe.transform(value, TOOLTIP_TIME_FORMAT) ?? '';
+ }
+ return (
+ this.formatValueForChart(value, labels, divisor, this.decimals) || value
+ ).toString();
+ },
+ customHTML: (_data, defaultHTML) => this.formatChartTooltip(defaultHTML)
},
points: {
enabled: false
};
}
- // Custom tooltip formatter to replace default timestamp with a formatted one.
- formatChartTooltip(defaultHTML: string, data: { date: Date }[]): string {
- if (!data?.length) return defaultHTML;
-
- const formattedTime = this.datePipe.transform(data[0].date, 'dd MMM, HH:mm:ss');
- return defaultHTML.replace(
- /<p class="value">.*?<\/p>/,
- `<p class="value">${formattedTime}</p>`
- );
+ formatChartTooltip(defaultHTML: string): string {
+ return defaultHTML.replace(/<p>x-value<\/p>/i, `<p>${$localize`Time`}</p>`);
}
// Uses number formatter service to convert chart value based on unit and divisor.