Implement support for quick device measurement metrics.

This commit is contained in:
Orne Brocaar
2022-06-28 15:05:42 +01:00
parent 4fa9341139
commit a01f8565fd
73 changed files with 8695 additions and 3833 deletions

View File

@ -0,0 +1,98 @@
import React, { Component } from "react";
import { Card, Empty } from "antd";
import { TimeUnit } from "chart.js";
import { Bar } from "react-chartjs-2";
import moment from "moment";
import { Metric, Aggregation } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb";
interface IProps {
metric: Metric;
aggregation: Aggregation;
}
class MetricBar extends Component<IProps> {
render() {
if (this.props.metric.getTimestampsList().length === 0 || this.props.metric.getDatasetsList().length === 0) {
return <Empty />;
}
let unit: TimeUnit = "hour";
if (this.props.aggregation === Aggregation.DAY) {
unit = "day";
} else if (this.props.aggregation === Aggregation.MONTH) {
unit = "month";
}
let backgroundColors = [
"#8bc34a",
"#ff5722",
"#ff9800",
"#ffc107",
"#ffeb3b",
"#cddc39",
"#4caf50",
"#009688",
"#00bcd4",
"#03a9f4",
"#2196f3",
"#3f51b5",
"#673ab7",
"#9c27b0",
"#e91e63",
];
const animation: false = false;
const options = {
animation: animation,
plugins: {
legend: {
display: true,
},
},
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
},
x: {
type: "time" as const,
time: {
unit: unit,
},
},
},
};
let data: {
labels: number[];
datasets: {
label: string;
data: number[];
backgroundColor: string;
}[];
} = {
labels: this.props.metric.getTimestampsList().map(v => moment(v.toDate()).valueOf()),
datasets: [],
};
for (let ds of this.props.metric.getDatasetsList()) {
data.datasets.push({
label: ds.getLabel(),
data: ds.getDataList(),
backgroundColor: backgroundColors.shift()!,
});
}
return (
<Card title={this.props.metric.getName()} className="dashboard-chart">
<Bar data={data} options={options} />
</Card>
);
}
}
export default MetricBar;

View File

@ -0,0 +1,81 @@
import React, { Component } from "react";
import { Card, Empty } from "antd";
import { TimeUnit } from "chart.js";
import { Line } from "react-chartjs-2";
import moment from "moment";
import { Metric, Aggregation } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb";
interface IProps {
metric: Metric;
aggregation: Aggregation;
zeroToNull?: boolean;
}
class MetricChart extends Component<IProps> {
render() {
if (this.props.metric.getTimestampsList().length === 0 || this.props.metric.getDatasetsList().length === 0) {
return <Empty />;
}
let unit: TimeUnit = "hour";
if (this.props.aggregation === Aggregation.DAY) {
unit = "day";
} else if (this.props.aggregation === Aggregation.MONTH) {
unit = "month";
}
const animation: false = false;
const options = {
animation: animation,
plugins: {
legend: {
display: false,
},
},
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
},
x: {
type: "time" as const,
time: {
unit: unit,
},
},
},
};
let data = {
labels: this.props.metric.getTimestampsList().map(v => moment(v.toDate()).valueOf()),
datasets: this.props.metric.getDatasetsList().map(v => {
return {
label: v.getLabel(),
borderColor: "rgba(33, 150, 243, 1)",
backgroundColor: "rgba(0, 0, 0, 0)",
lineTension: 0,
pointBackgroundColor: "rgba(33, 150, 243, 1)",
data: v.getDataList().map(v => {
if (v === 0 && this.props.zeroToNull) {
return null;
} else {
return v;
}
}),
};
}),
};
return (
<Card title={this.props.metric.getName()} className="dashboard-chart">
<Line height={75} options={options} data={data} />
</Card>
);
}
}
export default MetricChart;

View File

@ -1,44 +1,89 @@
import React, { Component } from "react";
import { color } from "chart.js/helpers";
import { Chart } from "react-chartjs-2";
import { Card, Empty } from "antd";
interface HeatmapData {
x: string;
y: Array<[string, number]>;
}
import { color } from "chart.js/helpers";
import { TimeUnit } from "chart.js";
import { Chart } from "react-chartjs-2";
import moment from "moment";
import { Metric, Aggregation } from "@chirpstack/chirpstack-api-grpc-web/common/common_pb";
interface IProps {
data: HeatmapData[];
metric: Metric;
fromColor: string;
toColor: string;
aggregation: Aggregation;
}
class Heatmap extends Component<IProps> {
class MetricHeatmap extends Component<IProps> {
render() {
if (this.props.data.length === 0) {
return null;
if (this.props.metric.getTimestampsList().length === 0 || this.props.metric.getDatasetsList().length === 0) {
return <Empty />;
}
let xSet: { [key: string]: any } = {};
let ySet: { [key: string]: any } = {};
let unit: TimeUnit = "hour";
if (this.props.aggregation === Aggregation.DAY) {
unit = "day";
} else if (this.props.aggregation === Aggregation.MONTH) {
unit = "month";
}
const animation: false = false;
let options = {
animation: animation,
maintainAspectRatio: false,
scales: {
y: {
type: "category" as const,
offset: true,
grid: {
display: false,
},
},
x: {
type: "time" as const,
time: {
unit: unit,
},
offset: true,
labels: this.props.metric.getTimestampsList().map(v => moment(v.toDate().valueOf())),
grid: {
display: false,
},
},
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
title: () => {
return "";
},
label: (ctx: any) => {
const v = ctx.dataset.data[ctx.dataIndex].v;
return "Count: " + v;
},
},
},
},
};
let dataData: {
x: string;
x: number;
y: string;
v: number;
}[] = [];
let data = {
labels: [],
labels: this.props.metric.getDatasetsList().map(v => v.getLabel()),
datasets: [
{
label: "Heatmap",
data: dataData,
minValue: -1,
maxValue: -1,
xSet: xSet,
ySet: ySet,
fromColor: this.props.fromColor.match(/\d+/g)!.map(Number),
toColor: this.props.toColor.match(/\d+/g)!.map(Number),
backgroundColor: (ctx: any): string => {
@ -64,80 +109,49 @@ class Heatmap extends Component<IProps> {
},
borderWidth: 0,
width: (ctx: any) => {
return (ctx.chart.chartArea || {}).width / Object.keys(ctx.dataset.xSet).length - 1;
return (ctx.chart.chartArea || {}).width / this.props.metric.getTimestampsList().length - 1;
},
height: (ctx: any) => {
return (ctx.chart.chartArea || {}).height / Object.keys(ctx.dataset.ySet).length - 1;
return (ctx.chart.chartArea || {}).height / this.props.metric.getDatasetsList().length - 1;
},
},
],
};
let xLabels: string[] = [];
data.labels.sort();
const animation: false = false;
const tsList = this.props.metric.getTimestampsList();
const dsList = this.props.metric.getDatasetsList();
let options = {
animation: animation,
maintainAspectRatio: false,
scales: {
y: {
type: "category" as const,
offset: true,
grid: {
display: false,
},
},
x: {
type: "time" as const,
offset: true,
labels: xLabels,
grid: {
display: false,
},
},
},
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
title: () => {
return "";
},
label: (ctx: any) => {
const v = ctx.dataset.data[ctx.dataIndex].v;
return "Count: " + v;
},
},
},
},
};
for (const row of this.props.data) {
options.scales.x.labels.push(row.x);
data.datasets[0].xSet[row.x] = {};
for (const y of row.y) {
data.datasets[0].ySet[y[0]] = {};
data.datasets[0].data.push({
x: row.x,
y: y[0],
v: y[1],
});
if (data.datasets[0].minValue === -1 || data.datasets[0].minValue > y[1]) {
data.datasets[0].minValue = y[1];
for (let i = 0; i < tsList.length; i++) {
for (let ds of dsList) {
let v = ds.getDataList()[i];
if (v === 0) {
continue;
}
if (data.datasets[0].maxValue < y[1]) {
data.datasets[0].maxValue = y[1];
data.datasets[0].data.push({
x: moment(tsList[i].toDate()).valueOf(),
y: ds.getLabel(),
v: v,
});
if (data.datasets[0].minValue === -1 || data.datasets[0].minValue > v) {
data.datasets[0].minValue = v;
}
if (data.datasets[0].maxValue < v) {
data.datasets[0].maxValue = v;
}
}
}
return <Chart type="matrix" data={data} options={options} />;
return (
<Card title={this.props.metric.getName()} className="dashboard-chart">
<Chart type="matrix" data={data} options={options} />
</Card>
);
}
}
export default Heatmap;
export default MetricHeatmap;