refactor(devtools): remove dependency on ngx-charts

This commit is contained in:
mgechev
2020-05-15 15:41:09 -07:00
committed by Minko Gechev
parent 3d0aee942e
commit 9170d6ae0b
13 changed files with 154 additions and 61 deletions
-1
View File
@@ -44,7 +44,6 @@
"@angular/material": "~9.2.0",
"@angular/platform-browser": "github:angular/platform-browser-builds#77ea10310c138f2fedf5fd625b567d8097ef8bf8",
"@angular/platform-browser-dynamic": "github:angular/platform-browser-dynamic-builds#d4a24f6679d86655a650ded46d78b4e290529f94",
"@swimlane/ngx-charts": "^14.0.0",
"memo-decorator": "^2.0.1",
"ngx-flamegraph": "0.0.7",
"rxjs": "~6.5.3",
@@ -5,7 +5,7 @@ import memo from 'memo-decorator';
export interface BargraphNode {
parents: ElementProfile[];
value: number;
name: string;
label: string;
original: ElementProfile;
}
@@ -31,11 +31,10 @@ export class BarGraphFormatter extends RecordFormatter<BargraphNode[]> {
timeSpent += super.getValue(element);
element.directives.forEach((dir) => {
const suffix = addSpaces(nodes.length);
const innerNode: BargraphNode = {
parents,
value: super.getDirectiveValue(dir),
name: dir.name + suffix,
label: dir.name,
original: element,
};
nodes.push(innerNode);
@@ -44,5 +43,3 @@ export class BarGraphFormatter extends RecordFormatter<BargraphNode[]> {
return timeSpent;
}
}
const addSpaces = (length: number) => ' '.repeat(length);
@@ -0,0 +1,7 @@
<div class="wrapper">
<div *ngFor="let bar of internalData; let i = index" class="element-wrapper">
<div (click)="select(i)" class="bar" [style.backgroundColor]="color" [style.width.%]="bar.value">
<span>{{ bar.label }}</span>
</div>
</div>
</div>
@@ -0,0 +1,31 @@
.bar {
height: 20px;
width: 0%;
margin-bottom: 7px;
padding-top: 3px;
padding-bottom: 3px;
opacity: 0.8;
cursor: pointer;
transition: opacity 0.3s ease-out, width 0.3s ease;
}
.bar span {
margin-left: 10px;
max-width: calc(100% - 20px);
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding-top: 3px;
}
.bar:hover {
opacity: 1;
}
:host,
.wrapper {
width: 100%;
height: 100%;
display: block;
}
@@ -0,0 +1,34 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
interface Data {
label: string;
value: number;
}
@Component({
selector: 'ng-bar-chart',
templateUrl: './bar-chart.component.html',
styleUrls: ['./bar-chart.component.scss'],
})
export class BarChartComponent {
@Input() set data(nodes: Data[]) {
this._originals = nodes;
this.internalData = [];
const max = nodes.reduce((a: number, c) => Math.max(a, c.value), -Infinity);
for (const node of nodes) {
this.internalData.push({
label: node.label,
value: (node.value / max) * 100,
});
}
}
@Input() color: string;
@Output() pick = new EventEmitter<Data>();
private _originals: Data[];
internalData: Data[] = [];
select(idx: number): void {
this.pick.emit(this._originals[idx]);
}
}
@@ -1,31 +1,13 @@
<div class="level-profile-wrapper" [class.split-width]="selectedEntry">
<ngx-charts-bar-horizontal
[view]="view"
[scheme]="colorScheme"
[results]="profileRecords"
[xAxis]="true"
[yAxis]="true"
[roundEdges]="false"
(select)="selectNode($event)"
>
</ngx-charts-bar-horizontal>
<ng-bar-chart (pick)="selectNode($event)" [color]="barColor" [data]="profileRecords"> </ng-bar-chart>
</div>
<div class="selected-entry" *ngIf="selectedEntry">
<header>{{ selectedEntry.name }} details</header>
<header>{{ selectedEntry.label }} details</header>
<section class="entry-statistics">
<div class="txt-total-time"><label>Total time spent:</label> {{ selectedEntry.value | number }}ms</div>
<div *ngIf="selectedEntry.value > 0">
<ngx-charts-pie-chart
legendTitle=""
[view]="pieChartView"
[scheme]="pieChartColorScheme"
[results]="pieChartData"
[tooltipText]="formatToolTip"
legend="true"
legendPosition="bottom"
>
</ngx-charts-pie-chart>
<ng-execution-details [data]="selectedDirectives"> </ng-execution-details>
</div>
<div *ngIf="parentHierarchy.length > 0">
<div class="txt-total-time"><label>Parent Hierarchy</label></div>
@@ -3,7 +3,8 @@ import { BargraphNode, BarGraphFormatter } from '../../record-formatter/bargraph
import { ProfilerFrame } from 'protocol';
export interface GraphNode {
name: string;
directive: string;
method: string;
value: number;
}
@@ -13,17 +14,10 @@ export interface GraphNode {
styleUrls: ['./bargraph-visualizer.component.scss'],
})
export class BargraphVisualizerComponent {
view: [number, number] = [600, 600];
colorScheme = {
domain: ['rgb(237, 213, 94)'],
};
barColor = 'rgb(237, 213, 94)';
profileRecords: BargraphNode[];
selectedEntry: BargraphNode | null = null;
pieChartView: [number, number] = [235, 200];
pieChartColorScheme = {
domain: ['#E71D36', '#2EC4B6', '#FF9F1C', '#011627'],
};
pieChartData: GraphNode[] = [];
selectedDirectives: GraphNode[] = [];
parentHierarchy: { name: string }[] = [];
private _formatter = new BarGraphFormatter();
@@ -31,22 +25,23 @@ export class BargraphVisualizerComponent {
@Input() set frame(data: ProfilerFrame) {
this.profileRecords = this._formatter.formatFrame(data);
this.selectedEntry = null;
this.view = [1000, this.profileRecords.length * 30];
}
formatPieChartData(bargraphNode: BargraphNode): GraphNode[] {
const graphData: GraphNode[] = [];
bargraphNode.original.directives.forEach((node) => {
const {changeDetection} = node;
const { changeDetection } = node;
if (changeDetection) {
graphData.push({
name: `${node.name} changeDetection`,
directive: node.name,
method: 'changes',
value: parseFloat(changeDetection.toFixed(2)),
});
}
Object.keys(node.lifecycle).forEach((key) => {
graphData.push({
name: `${node.name} ${key}`,
directive: node.name,
method: key,
value: +node.lifecycle[key].toFixed(2),
});
});
@@ -54,13 +49,13 @@ export class BargraphVisualizerComponent {
return graphData;
}
selectNode(data: any): void {
const index = this.profileRecords.findIndex((element) => element.name === data.name);
selectNode(data: BargraphNode): void {
const index = this.profileRecords.findIndex((element) => element.label === data.label);
this.selectedEntry = this.profileRecords[index];
this.parentHierarchy = this.selectedEntry.parents.map((element) => {
return { name: element.directives[0].name };
});
this.pieChartData = this.formatPieChartData(this.selectedEntry);
this.selectedDirectives = this.formatPieChartData(this.selectedEntry);
}
formatToolTip(data: any): string {
@@ -0,0 +1,20 @@
<table>
<thead>
<th class="name">Directive</th>
<th class="method">Method</th>
<th class="value">Time</th>
</thead>
<tbody>
<tr *ngFor="let entry of data">
<td class="name">
{{ entry.directive }}
</td>
<td class="method">
{{ entry.method }}
</td>
<td class="value">
{{ entry.value }}
</td>
</tr>
</tbody>
</table>
@@ -0,0 +1,13 @@
.name {
width: 80px;
overflow: hidden;
text-overflow: ellipsis;
}
.value {
width: 30px;
}
.method {
width: 50px;
}
@@ -0,0 +1,16 @@
import { Component, Input } from '@angular/core';
export interface GraphNode {
directive: string;
method: string;
value: number;
}
@Component({
selector: 'ng-execution-details',
templateUrl: './execution-details.component.html',
styleUrls: ['./execution-details.component.scss'],
})
export class ExecutionDetailsComponent {
@Input() data: GraphNode[];
}
@@ -20,16 +20,7 @@
<label>Did not run change detection</label>
</div>
<div *ngIf="selectedEntry.value > 0">
<ngx-charts-pie-chart
legendTitle=""
[view]="view"
[scheme]="colorScheme"
[results]="graphData"
[tooltipText]="formatToolTip"
legend="true"
legendPosition="bottom"
>
</ngx-charts-pie-chart>
<ng-execution-details [data]="graphData"></ng-execution-details>
</div>
</section>
</div>
@@ -10,7 +10,8 @@ import { RawData } from 'ngx-flamegraph/lib/utils';
import { ProfilerFrame } from 'protocol';
export interface GraphNode {
name: string;
directive: string;
method: string;
value: number;
}
@@ -60,13 +61,15 @@ export class FlamegraphVisualizerComponent {
const changeDetection = node.changeDetection;
if (changeDetection !== undefined) {
graphData.push({
name: `${node.name} changeDetection`,
directive: node.name,
method: 'changes',
value: parseFloat(changeDetection.toFixed(2)),
});
}
Object.keys(node.lifecycle).forEach((key) => {
graphData.push({
name: `${node.name} ${key}`,
directive: node.name,
method: key,
value: +node.lifecycle[key].toFixed(2),
});
});
@@ -1,21 +1,26 @@
import { NgModule } from '@angular/core';
import { TimelineVisualizerComponent } from './timeline-visualizer.component';
import { CommonModule } from '@angular/common';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { NgxFlamegraphModule } from 'ngx-flamegraph';
import { NgxChartsModule } from '@swimlane/ngx-charts';
import { FlamegraphVisualizerComponent } from './flamegraph-visualizer/flamegraph-visualizer.component';
import { BargraphVisualizerComponent } from './bargraph-visualizer/bargraph-visualizer.component';
import { TreeMapVisualizerComponent } from './tree-map-visualizer/tree-map-visualizer.component';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { TimelineVisualizerComponent } from './timeline-visualizer.component';
import { BarChartComponent } from './bar-chart/bar-chart.component';
import { ExecutionDetailsComponent } from './execution-details/execution-details.component';
@NgModule({
declarations: [
ExecutionDetailsComponent,
BarChartComponent,
TimelineVisualizerComponent,
FlamegraphVisualizerComponent,
TreeMapVisualizerComponent,
BargraphVisualizerComponent,
],
imports: [CommonModule, NgxFlamegraphModule, NgxChartsModule, MatCheckboxModule],
imports: [CommonModule, NgxFlamegraphModule, MatCheckboxModule],
exports: [TimelineVisualizerComponent],
})
export class RecordingVisualizerModule {}