提交 0a0bec61 编写于 作者: S Sebastian Florek 提交者: Kubernetes Prow Robot

Use ngx-charts to display graphs (#4620)

* Use ngx-charts to display graphs

* Fix formatting

* Fix test

* Increase default jasmine timeout
上级 94615eaf
jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
......@@ -26,6 +26,11 @@ module.exports = function(config) {
pattern: './node_modules/@angular/material/prebuilt-themes/indigo-pink.css',
included: true,
watched: true
},
{
pattern: './aio/karma-test-shim.js',
included: true,
watched: true,
}
],
......
此差异已折叠。
......@@ -282,4 +282,10 @@
.c3-tooltip {
color: $dark-primary-text;
}
.ngx-charts {
text {
fill: mat-color($foreground-palette, text) !important;
}
}
}
......@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {AfterViewInit, Component, Input, OnChanges} from '@angular/core';
import {Component, Input, OnChanges} from '@angular/core';
import {ChartAPI, generate} from 'c3';
import {BaseType, select, Selection} from 'd3';
......
......@@ -12,84 +12,103 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {AfterViewInit, Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
import {Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core';
import {Metric} from '@api/backendapi';
import {generate} from 'c3';
import {timeFormat} from 'd3';
import {select} from 'd3-selection';
import {curveBasis, CurveFactory, timeFormat} from 'd3';
import {coresFilter, memoryFilter} from './helper';
import {compareCoreSuffix, compareMemorySuffix, coresFilter, memoryFilter} from './helper';
export enum GraphType {
CPU = 'cpu',
Memory = 'memory',
}
@Component({selector: 'kd-graph', templateUrl: './template.html'})
export class GraphComponent implements OnInit, AfterViewInit, OnChanges {
export class GraphComponent implements OnInit, OnChanges {
@Input() metric: Metric;
@Input() id: string;
private name: string;
@Input() graphType: GraphType = GraphType.CPU;
series: Array<{name: string; series: Array<{value: number; name: string}>}> = [];
curve = curveBasis;
customColors = {};
yAxisLabel = '';
yAxisTickFormatting = (value: number) => `${value} ${this.yAxisSuffix_}`;
private suffixMap_: Map<number, string> = new Map<number, string>();
private yAxisSuffix_ = '';
ngOnInit(): void {
if (this.id) {
this.name = this.id;
this.id = this.id.replace(/\s/g, '');
if (!this.graphType) {
throw new Error('Graph type has to be provided.');
}
}
ngAfterViewInit(): void {
if (this.metric && this.id) {
this.generateGraph();
}
this.series = this.generateSeries_();
this.customColors = this.getColor_();
this.yAxisLabel = this.graphType === GraphType.CPU ? 'CPU (cores)' : 'Memory (bytes)';
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['id']) {
this.name = changes['id'].currentValue;
}
ngOnChanges(_: SimpleChanges): void {
this.suffixMap_.clear();
this.series = this.generateSeries_();
}
if (changes['metric']) {
this.metric = changes['metric'].currentValue;
this.generateGraph();
}
getTooltipValue(value: number): string {
return `${value} ${this.suffixMap_.has(value) ? this.suffixMap_.get(value) : ''}`;
}
private generateGraph() {
generate({
bindto: select(`#${this.id}`),
size: {
height: 200,
},
padding: {
right: 25,
private generateSeries_(): Array<{name: string; series: Array<{value: number; name: string}>}> {
return [
{
name: this.id,
series: this.metric.dataPoints.map(point => {
return {
value: this.normalize_(point.y),
name: timeFormat('%H:%M')(new Date(1000 * point.x)),
};
}),
},
data: {
x: 'x',
columns: [
['x', ...this.metric.dataPoints.map(dp => dp.x)],
[this.name, ...this.metric.dataPoints.map(dp => dp.y)],
],
types: {
'CPU Usage': 'area-spline',
'Memory Usage': 'area-spline',
},
colors: {
'CPU Usage': '#00c752',
'Memory Usage': '#326de6',
},
},
axis: {
x: {
tick: {
format: (x: number): string => {
return timeFormat('%H:%M')(new Date(1000 * x));
},
];
}
private getColor_(): Array<{name: string; value: string}> {
return this.graphType === GraphType.CPU
? [
{
name: this.id,
value: '#00c752',
},
label: 'Time',
},
y: {
tick: {
format: this.name.includes('CPU') ? coresFilter : memoryFilter,
]
: [
{
name: this.id,
value: '#326de6',
},
label: this.name.includes('CPU') ? 'CPU (Cores)' : 'Memory (bytes)',
},
},
});
];
}
private normalize_(data: number): number {
const filtered = this.graphType === GraphType.CPU ? coresFilter(data) : memoryFilter(data);
const parts = filtered.split(' ');
const value = Number(parts[0]);
if (parts.length > 1) {
this.suffixMap_.set(value, parts[1]);
switch (this.graphType) {
case GraphType.CPU:
this.yAxisSuffix_ =
compareCoreSuffix(this.yAxisSuffix_, parts[1]) === -1 ? parts[1] : this.yAxisSuffix_;
break;
case GraphType.Memory:
this.yAxisSuffix_ =
compareMemorySuffix(this.yAxisSuffix_, parts[1]) === -1 ? parts[1] : this.yAxisSuffix_;
break;
default:
this.yAxisSuffix_ = '';
}
}
return value;
}
}
......@@ -20,6 +20,13 @@ const coreBase = 1000;
/** Names of the suffixes where I-th name is for base^I suffix. */
const corePowerSuffixes = ['', 'k', 'M', 'G', 'T'];
export function compareCoreSuffix(first: string, second: string): number {
const firstIdx = corePowerSuffixes.indexOf(first);
const secondIdx = corePowerSuffixes.indexOf(second);
return firstIdx === secondIdx ? 0 : firstIdx > secondIdx ? 1 : -1;
}
function precisionFilter(d: number): string {
if (d >= 1000) {
return format(',')(Number(d.toPrecision(3)));
......@@ -57,6 +64,13 @@ const memoryBase = 1024;
/** Names of the suffixes where I-th name is for base^I suffix. */
const memoryPowerSuffixes = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi'];
export function compareMemorySuffix(first: string, second: string): number {
const firstIdx = memoryPowerSuffixes.indexOf(first);
const secondIdx = memoryPowerSuffixes.indexOf(second);
return firstIdx === secondIdx ? 0 : firstIdx > secondIdx ? 1 : -1;
}
/**
* Returns filter function that formats memory in bytes.
*/
......
......@@ -14,6 +14,19 @@ See the License for the specific language governing permissions and
limitations under the License.
-->
<div [id]="id">
<!-- Graph <svg> is inserted in here. -->
</div>
<ngx-charts-area-chart fxFlex
[xAxis]="true"
[yAxis]="true"
[showYAxisLabel]="true"
[yAxisLabel]="yAxisLabel"
[yAxisTickFormatting]="yAxisTickFormatting"
[results]="series"
[showGridLines]="true"
[curve]="curve"
[customColors]="customColors"
[gradient]="true">
<ng-template #seriesTooltipTemplate
let-model="model">
<pre>{{model[0].series}}: {{getTooltipValue(model[0].value)}}</pre>
</ng-template>
</ngx-charts-area-chart>
......@@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {SimpleChange} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {Metric} from '@api/backendapi';
import {SharedModule} from '../../../shared.module';
import {CardComponent} from '../card/component';
import {GraphComponent} from '../graph/component';
import {GraphComponent, GraphType} from '../graph/component';
import {GraphCardComponent} from './component';
......@@ -68,6 +67,7 @@ describe('GraphCardComponent', () => {
component.graphTitle = 'CPU';
component.selectedMetricName = 'cpu/usage';
component.selectedMetric = testMetrics[0];
component.graphType = GraphType.CPU;
testHostFixture.detectChanges();
expect(component.shouldShowGraph()).toBeTruthy();
......
......@@ -14,11 +14,13 @@
import {Component, Input, OnChanges, SimpleChanges} from '@angular/core';
import {Metric} from '@api/backendapi';
import {GraphType} from '../graph/component';
@Component({selector: 'kd-graph-card', templateUrl: './template.html'})
export class GraphCardComponent implements OnChanges {
@Input() graphTitle: string;
@Input() graphInfo: string;
@Input() graphType: GraphType;
@Input() metrics: Metric[];
@Input() selectedMetricName: string;
selectedMetric: Metric;
......
......@@ -20,6 +20,7 @@ limitations under the License.
fxLayout="row">{{graphTitle}} <span *ngIf="graphInfo">{{graphTitle}}</span></div>
<div content>
<kd-graph [id]="graphTitle"
[graphType]="graphType"
[metric]="selectedMetric"></kd-graph>
</div>
</kd-card>
......@@ -14,6 +14,7 @@
import {Component, Input} from '@angular/core';
import {Metric} from '@api/backendapi';
import {GraphType} from '../graph/component';
@Component({
selector: 'kd-graph-metrics',
......@@ -23,6 +24,8 @@ import {Metric} from '@api/backendapi';
export class GraphMetricsComponent {
@Input() metrics: Metric[];
readonly GraphType: typeof GraphType = GraphType;
showGraphs(): boolean {
return (
this.metrics &&
......
......@@ -18,11 +18,13 @@ limitations under the License.
*ngIf="showGraphs()">
<div class="graph-col">
<kd-graph-card graphTitle="CPU Usage"
[graphType]="GraphType.CPU"
[metrics]="metrics"
selectedMetricName="cpu/usage_rate"></kd-graph-card>
</div>
<div class="graph-col">
<kd-graph-card graphTitle="Memory Usage"
[graphType]="GraphType.Memory"
[metrics]="metrics"
selectedMetricName="memory/usage"></kd-graph-card>
</div>
......
......@@ -45,7 +45,11 @@ export class ResourceService<T> extends ResourceBase<T> {
return timer(0, interval);
}),
)
.pipe(switchMapTo(this.http_.get<T>(endpoint, {params})))
.pipe(
switchMapTo(
this.http_.get<T>(endpoint, {params}),
),
)
.pipe(publishReplay(1))
.pipe(refCount());
}
......@@ -85,7 +89,11 @@ export class NamespacedResourceService<T> extends ResourceBase<T> {
return timer(0, interval);
}),
)
.pipe(switchMapTo(this.http_.get<T>(endpoint, {params})))
.pipe(
switchMapTo(
this.http_.get<T>(endpoint, {params}),
),
)
.pipe(publishReplay(1))
.pipe(refCount());
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册