Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
feat(event-chart): Use duration pipe in axis
Browse files Browse the repository at this point in the history
  • Loading branch information
subarroca authored and tomheller committed May 5, 2020
1 parent 214f496 commit b951077
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import {
DtEventChartOverlay,
} from './event-chart-directives';
import { DtEventChartLegend } from './event-chart-legend';
import {
DtFormattersModule,
DtDuration,
} from '@dynatrace/barista-components/formatters';

export const DT_EVENT_CHART_DIRECTIVES = [
DtEventChart,
Expand All @@ -39,7 +43,8 @@ export const DT_EVENT_CHART_DIRECTIVES = [

@NgModule({
exports: DT_EVENT_CHART_DIRECTIVES,
imports: [CommonModule, DtLegendModule, DtOverlayModule],
imports: [CommonModule, DtLegendModule, DtOverlayModule, DtFormattersModule],
declarations: [DtEventChartLegend, ...DT_EVENT_CHART_DIRECTIVES],
providers: [DtDuration],
})
export class DtEventChartModule {}
59 changes: 23 additions & 36 deletions libs/barista-components/event-chart/src/event-chart.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { By } from '@angular/platform-browser';
import { DtEventChartModule } from '@dynatrace/barista-components/event-chart';

import { dispatchFakeEvent } from '@dynatrace/testing/browser';
import { DtEventChart, roundUp, formatRelativeTimestamp } from './event-chart';
import { DtEventChart } from './event-chart';
import { DtEventChartSelectedEvent } from './event-chart-directives';
import {
DT_UI_TEST_CONFIG,
Expand All @@ -37,7 +37,7 @@ function getRenderedMergedTextLabels(fixture: ComponentFixture<any>): string[] {
const texts = fixture.debugElement.queryAll(
By.css('.dt-event-chart-event-mergednumber'),
);
return texts.map(text => text.nativeElement.innerHTML.trim());
return texts.map((text) => text.nativeElement.innerHTML.trim());
}

/** Gets the rendered path that connects the rendered Event bubbles. */
Expand All @@ -61,7 +61,7 @@ function getLegendItems(
const legendItemElements = fixture.debugElement.queryAll(
By.css('.dt-legend-item'),
);
return legendItemElements.map(element => {
return legendItemElements.map((element) => {
const label = element
.query(By.css('.dt-legend-item-label'))
.nativeElement.textContent.trim();
Expand Down Expand Up @@ -98,7 +98,7 @@ function getLaneLabels(fixture: ComponentFixture<any>): string[] {
const laneLabels = fixture.debugElement.queryAll(
By.css('.dt-event-chart-lane-label'),
);
return laneLabels.map(text => text.nativeElement.innerHTML.trim());
return laneLabels.map((text) => text.nativeElement.innerHTML.trim());
}

describe('DtEventChart', () => {
Expand Down Expand Up @@ -199,7 +199,7 @@ describe('DtEventChart', () => {
const splitPath = path
.replace(/ ([M,L,C,A])/gim, ' *$1')
.split('*')
.map(pathInstruction => pathInstruction.trim())
.map((pathInstruction) => pathInstruction.trim())
.map((pathInstruction: string): {
key: string;
x: number;
Expand All @@ -216,7 +216,7 @@ describe('DtEventChart', () => {
});

// Expect instructions to be correct
expect(splitPath.map(e => e.key)).toEqual([
expect(splitPath.map((e) => e.key)).toEqual([
'M',
'L',
'L',
Expand Down Expand Up @@ -674,37 +674,24 @@ describe('DtEventChart', () => {
);
expect(selectedEvent).toBeNull();
});
});

describe('x-axis', () => {
describe('formatRelativeTimestamp', () => {
it('should return milliseconds', () => {
expect(formatRelativeTimestamp(10)).toBe('10 ms');
});

it('should return seconds', () => {
expect(formatRelativeTimestamp(10_000)).toBe('10 s');
});

it('should return minutes', () => {
expect(formatRelativeTimestamp(600_000)).toBe('10 min');
});

it('should return hours', () => {
expect(formatRelativeTimestamp(2 * 60 * 60_000)).toBe('2 h');
});

it('should return days', () => {
expect(formatRelativeTimestamp(1.5 * 24 * 60 * 60_000)).toBe('1.5 d');
});
});
describe('roundUp', () => {
it('should return a rounded up number of 2 decimals', () => {
expect(roundUp(1.325, 2)).toBe(1.33);
});

it('should return less decimals if possible', () => {
expect(roundUp(1, 2)).toBe(1);
describe('x-axis', () => {
it('should have formatted ticks', () => {
const axisLabels = fixture.debugElement
.queryAll(By.css('.dt-event-chart-tick-label'))
.map((el) => el.nativeElement.textContent.trim());

expect(axisLabels).toEqual([
'0 s',
'0.01 s',
'0.02 s',
'0.03 s',
'0.04 s',
'0.05 s',
'0.06 s',
'0.07 s',
'0.08 s',
]);
});
});
});
Expand Down
109 changes: 60 additions & 49 deletions libs/barista-components/event-chart/src/event-chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,18 @@ import { DtEventChartLegend } from './event-chart-legend';
import { dtCreateEventPath } from './merge-and-path/create-event-path';
import { dtEventChartMergeEvents } from './merge-and-path/merge-events';
import { RenderEvent } from './render-event.interface';
import {
DtDuration,
DtTimeUnit,
DtFormattedValue,
} from '@dynatrace/barista-components/formatters';

const EVENT_BUBBLE_SIZE = 16;
const EVENT_BUBBLE_SPACING = 4;

const EVENT_BUBBLE_OVERLAP_THRESHOLD = EVENT_BUBBLE_SIZE / 2;
const TICK_HEIGHT = 24;
const TICK_WIDTH = 100;

const LANE_HEIGHT = EVENT_BUBBLE_SIZE * 3;

Expand Down Expand Up @@ -212,7 +218,7 @@ export class DtEventChart<T> implements AfterContentInit, OnInit, OnDestroy {
_renderPath: string | null = null;

/** @internal X axis ticks that are being rendered in the svgCanvas. */
_renderTicks: { x: number; value: string }[] = [];
_renderTicks: { x: number; value: string | DtFormattedValue }[] = [];

/** @internal The width of the svg. */
_svgWidth = 0;
Expand Down Expand Up @@ -251,6 +257,7 @@ export class DtEventChart<T> implements AfterContentInit, OnInit, OnDestroy {
private _appRef: ApplicationRef,
private _injector: Injector,
private _overlayService: Overlay,
private _durationPipe: DtDuration,
@Inject(DOCUMENT) private _document: any,
private _platform: Platform,
/** @breaking-change: `_elementRef` will be mandatory with version 7.0.0 */
Expand All @@ -269,14 +276,18 @@ export class DtEventChart<T> implements AfterContentInit, OnInit, OnDestroy {
const eventChanges$ = this._events.changes.pipe(
takeUntil(this._destroy$),
switchMap(() =>
merge(...this._events.map(e => e._stateChanges$)).pipe(startWith(null)),
merge(...this._events.map((e) => e._stateChanges$)).pipe(
startWith(null),
),
),
);
// Get all state-changes events from the dt-event-chart-lane content children.
const laneChanges$ = this._lanes.changes.pipe(
takeUntil(this._destroy$),
switchMap(() =>
merge(...this._lanes.map(e => e._stateChanges$)).pipe(startWith(null)),
merge(...this._lanes.map((e) => e._stateChanges$)).pipe(
startWith(null),
),
),
);

Expand Down Expand Up @@ -422,29 +433,31 @@ export class DtEventChart<T> implements AfterContentInit, OnInit, OnDestroy {
path.push(`M ${renderEvent.x1 - eventBubbleRadius} ${renderEvent.y}`);
// Curve up from center to top left
path.push(
`A ${eventBubbleRadius} ${eventBubbleRadius} 0 0 1 ${
renderEvent.x1
} ${renderEvent.y - eventBubbleRadius}`,
`A ${eventBubbleRadius} ${eventBubbleRadius} 0 0 1 ${renderEvent.x1} ${
renderEvent.y - eventBubbleRadius
}`,
);
// Path the top border
path.push(`H ${renderEvent.x2}`);
// Curve to down from top right to center
path.push(
`A ${eventBubbleRadius} ${eventBubbleRadius} 0 0 1 ${renderEvent.x2 +
eventBubbleRadius} ${renderEvent.y}`,
`A ${eventBubbleRadius} ${eventBubbleRadius} 0 0 1 ${
renderEvent.x2 + eventBubbleRadius
} ${renderEvent.y}`,
);
// Curve to down from center to bottom right
path.push(
`A ${eventBubbleRadius} ${eventBubbleRadius} 0 0 1 ${
renderEvent.x2
} ${renderEvent.y + eventBubbleRadius}`,
`A ${eventBubbleRadius} ${eventBubbleRadius} 0 0 1 ${renderEvent.x2} ${
renderEvent.y + eventBubbleRadius
}`,
);
// Path the bottom border
path.push(`H ${renderEvent.x1}`);
// Curve to up from bottom left to center
path.push(
`A ${eventBubbleRadius} ${eventBubbleRadius} 0 0 1 ${renderEvent.x1 -
eventBubbleRadius} ${renderEvent.y}`,
`A ${eventBubbleRadius} ${eventBubbleRadius} 0 0 1 ${
renderEvent.x1 - eventBubbleRadius
} ${renderEvent.y}`,
);
return path.join(' ');
}
Expand Down Expand Up @@ -624,7 +637,7 @@ export class DtEventChart<T> implements AfterContentInit, OnInit, OnDestroy {
private _updateRenderLanes(): void {
let y = 1;
this._renderLanes = this._lanes
.map(lane => {
.map((lane) => {
const renderLane = { lane, y };
y += LANE_HEIGHT + 1;
return renderLane;
Expand Down Expand Up @@ -695,14 +708,16 @@ export class DtEventChart<T> implements AfterContentInit, OnInit, OnDestroy {
/** Generates and updates the ticks for the x-axis. */
private _updateTicks(min: number, max: number): void {
const timeScale = this._getTimeScaleForEvents(min, max);
const dateTicks = timeScale.ticks();
this._renderTicks = dateTicks.map(date => {

const tickAmount = Math.floor(
(timeScale.range()[1] - timeScale.range()[0]) / TICK_WIDTH,
);
const dateTicks = timeScale.ticks(tickAmount);
this._renderTicks = dateTicks.map((date) => {
const timestamp = date.getTime();
return {
x: timeScale(timestamp),
// TODO @thomas.pink, @thomas.heller:
// Investigate if we should move the time formatting to a pipe
value: formatRelativeTimestamp(timestamp),
value: this._formatRelativeTimestamp(timestamp),
};
});
}
Expand Down Expand Up @@ -765,7 +780,7 @@ export class DtEventChart<T> implements AfterContentInit, OnInit, OnDestroy {

/** Tries to find a lane base on a provided name. */
private _getLaneByName(name: string): DtEventChartLane | null {
return this._lanes.find(lane => lane.name === name) || null;
return this._lanes.find((lane) => lane.name === name) || null;
}

/** Returns the position (reversed index) of a lane. */
Expand Down Expand Up @@ -803,39 +818,35 @@ export class DtEventChart<T> implements AfterContentInit, OnInit, OnDestroy {

patternDefsOutlet = outlet;
}

/** Formats a relative timestamp into a readable text. */
private _formatRelativeTimestamp(
timestamp: number,
): string | DtFormattedValue {
// TODO: once duration pipe returns a nice value maybe the unit is automatically chosen
let outputUnit = DtTimeUnit.SECOND;

const sec = 1000;
const min = sec * 60;
const hour = min * 60;
const day = hour * 24;

if (timestamp >= day) {
outputUnit = DtTimeUnit.DAY;
} else if (timestamp >= hour) {
outputUnit = DtTimeUnit.HOUR;
} else if (timestamp >= min) {
outputUnit = DtTimeUnit.MINUTE;
} else if (timestamp >= sec) {
outputUnit = DtTimeUnit.SECOND;
}

return this._durationPipe.transform(timestamp, 'PRECISE', outputUnit);
}
}

/** Determines if a passed parameter is part of the DtEventChartColors. */
// tslint:disable-next-line: no-any
function isValidColor(color: any): color is DtEventChartColors {
return isDefined(color) && DT_EVENT_CHART_COLORS.indexOf(color) !== -1;
}

/** Formats a relative timestamp into a readable text. */
export function formatRelativeTimestamp(timestamp: number): string {
const sec = 1000;
const min = sec * 60;
const hour = min * 60;
const day = hour * 24;
const decimals = 2;
if (timestamp >= day) {
return `${roundUp(timestamp / day, decimals)} d`;
} else if (timestamp >= hour) {
return `${roundUp(timestamp / hour, decimals)} h`;
} else if (timestamp >= min) {
return `${roundUp(timestamp / min, decimals)} min`;
} else if (timestamp >= sec) {
return `${roundUp(timestamp / sec, decimals)} s`;
}
return `${timestamp} ms`;
}

/**
* Rounds the given number to the specified decimals.
* @param num number to be rounded
* @param decimals maximum amount of decimals
* @returns rounded number
*/
export function roundUp(num: number, decimals: number): number {
return Math.round(10 ** decimals * num) / 10 ** decimals;
}

0 comments on commit b951077

Please sign in to comment.