mirror of
https://github.com/chirpstack/chirpstack.git
synced 2025-06-16 06:18:27 +00:00
Implement support for quick device measurement metrics.
This commit is contained in:
98
ui/src/components/MetricBar.tsx
Normal file
98
ui/src/components/MetricBar.tsx
Normal 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;
|
81
ui/src/components/MetricChart.tsx
Normal file
81
ui/src/components/MetricChart.tsx
Normal 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;
|
@ -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;
|
Reference in New Issue
Block a user