add working bar charts, before restructureing of code
This commit is contained in:
parent
aef9ee4907
commit
0533a86b11
|
@ -3,19 +3,25 @@ import { RecordSeries } from "../../../dataCache/data";
|
||||||
import {
|
import {
|
||||||
GraphCoordinates,
|
GraphCoordinates,
|
||||||
GraphData,
|
GraphData,
|
||||||
mergeDeep,
|
flattenBarGraphData,
|
||||||
|
flattenToggles,
|
||||||
|
insertTreeElements,
|
||||||
|
isNumeric,
|
||||||
|
isText,
|
||||||
parseCsv,
|
parseCsv,
|
||||||
|
stringToColor,
|
||||||
|
transformToBarGraphData,
|
||||||
} from "../../../util/graph.util";
|
} from "../../../util/graph.util";
|
||||||
import { TimeRange, TimeSpan, UnixTime } from "../../../dataCache/time";
|
import { TimeRange, TimeSpan, UnixTime } from "../../../dataCache/time";
|
||||||
import { useContext, useEffect, useMemo, useState } from "react";
|
import { useContext, useEffect, useMemo, useState } from "react";
|
||||||
import { BehaviorSubject, startWith, throttleTime, withLatestFrom } from "rxjs";
|
import { BehaviorSubject, startWith, throttleTime, withLatestFrom } from "rxjs";
|
||||||
import { S3Access } from "../../../dataCache/S3/S3Access";
|
import { S3Access } from "../../../dataCache/S3/S3Access";
|
||||||
import DataCache, { FetchResult } from "../../../dataCache/dataCache";
|
import DataCache, { FetchResult } from "../../../dataCache/dataCache";
|
||||||
|
|
||||||
import { LogContext } from "../../Context/LogContextProvider";
|
import { LogContext } from "../../Context/LogContextProvider";
|
||||||
import { TreeElement, ToggleElement } from "./CheckboxTree";
|
import { TreeElement, ToggleElement } from "./CheckboxTree";
|
||||||
import { isDefined } from "../../../dataCache/utils/maybe";
|
import { isDefined } from "../../../dataCache/utils/maybe";
|
||||||
import { timeStamp } from "console";
|
|
||||||
|
import { Data } from "plotly.js";
|
||||||
|
|
||||||
export const createTimes = (
|
export const createTimes = (
|
||||||
range: TimeRange,
|
range: TimeRange,
|
||||||
|
@ -39,8 +45,8 @@ const ScalarGraph = () => {
|
||||||
);
|
);
|
||||||
const [timeSeries, setTimeSeries] = useState<RecordSeries>([]);
|
const [timeSeries, setTimeSeries] = useState<RecordSeries>([]);
|
||||||
const [range, setRange] = useState([
|
const [range, setRange] = useState([
|
||||||
timeRange[0].toDate().getTime(),
|
timeRange[0].toDate(),
|
||||||
timeRange[timeRange.length - 1].toDate().getTime(),
|
timeRange[timeRange.length - 1].toDate(),
|
||||||
]);
|
]);
|
||||||
const [uiRevision, setUiRevision] = useState(Math.random());
|
const [uiRevision, setUiRevision] = useState(Math.random());
|
||||||
const [plotTitles, setPlotTitles] = useState<string[]>([]);
|
const [plotTitles, setPlotTitles] = useState<string[]>([]);
|
||||||
|
@ -54,43 +60,11 @@ const ScalarGraph = () => {
|
||||||
"saliomameiringen",
|
"saliomameiringen",
|
||||||
"sos-ch-dk-2",
|
"sos-ch-dk-2",
|
||||||
"exo.io",
|
"exo.io",
|
||||||
"EXO18e7ae9e53fae71ee55cf35b",
|
"EXO464a9ff62fdfa407aa742855",
|
||||||
"3Cyonq8gMQ0a3elTH2vP7Yv-czcCj8iE2lBcPB9XhSc",
|
"f2KtCWN4EHFqtvH2kotdyI0w5SjjdHVPAADdcD3ik8g",
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
|
||||||
const insert = (
|
|
||||||
children: TreeElement[] = [],
|
|
||||||
[head, ...tail]: string[]
|
|
||||||
): TreeElement[] => {
|
|
||||||
let child = children.find((child) => child.name === head);
|
|
||||||
|
|
||||||
if (!child) {
|
|
||||||
children.push(
|
|
||||||
(child = {
|
|
||||||
id: head,
|
|
||||||
name: head,
|
|
||||||
children: [],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (tail.length > 0) {
|
|
||||||
insert(child.children, tail);
|
|
||||||
}
|
|
||||||
return children;
|
|
||||||
};
|
|
||||||
|
|
||||||
const flattenToggles = (toggles: TreeElement[]): ToggleElement => {
|
|
||||||
return toggles.reduce((acc, current) => {
|
|
||||||
if (current.children.length > 0) {
|
|
||||||
acc[current.id] = false;
|
|
||||||
return { ...acc, ...flattenToggles(current.children) };
|
|
||||||
}
|
|
||||||
acc[current.id] = false;
|
|
||||||
return acc;
|
|
||||||
}, {} as ToggleElement);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = cache.gotData
|
const subscription = cache.gotData
|
||||||
.pipe(
|
.pipe(
|
||||||
|
@ -118,7 +92,7 @@ const ScalarGraph = () => {
|
||||||
.slice(1);
|
.slice(1);
|
||||||
})
|
})
|
||||||
.reduce(
|
.reduce(
|
||||||
(children, path) => insert(children, path),
|
(children, path) => insertTreeElements(children, path),
|
||||||
[] as TreeElement[]
|
[] as TreeElement[]
|
||||||
);
|
);
|
||||||
setToggles(treeElements);
|
setToggles(treeElements);
|
||||||
|
@ -156,36 +130,30 @@ const ScalarGraph = () => {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const transformToGraphData = (timeStampData: RecordSeries) => {
|
const transformToGraphData = (input: RecordSeries): GraphData => {
|
||||||
const graphData = timeStampData.reduce((acc, curr) => {
|
const transformedObject: any = {};
|
||||||
if (isDefined(curr.value)) {
|
input.forEach((item) => {
|
||||||
const timeStampObj = Object.keys(curr.value).reduce(
|
if (isDefined(item.value)) {
|
||||||
(pathAcc, currPath) => {
|
Object.keys(item.value).forEach((key) => {
|
||||||
if (currPath) {
|
if (!transformedObject.hasOwnProperty(key)) {
|
||||||
return {
|
transformedObject[key] = {
|
||||||
...pathAcc,
|
x: [],
|
||||||
[currPath]: {
|
y: [],
|
||||||
x: [new Date(curr.time.ticks * 1000)],
|
|
||||||
y: [curr.value ? curr.value[currPath] : 0],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return pathAcc;
|
transformedObject[key].x.push(
|
||||||
},
|
new Date(item.time.ticks * 1000).toISOString()
|
||||||
{} as GraphData
|
|
||||||
);
|
);
|
||||||
|
transformedObject[key].y.push(item.value?.[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
if (plotTitles.length === 0) {
|
if (plotTitles.length === 0) {
|
||||||
setPlotTitles(Object.keys(curr.value));
|
setPlotTitles(Object.keys(transformedObject));
|
||||||
}
|
}
|
||||||
return mergeDeep(acc, timeStampObj);
|
});
|
||||||
}
|
return Object.keys(transformedObject).length > 0
|
||||||
return acc;
|
? transformedObject
|
||||||
}, {} as GraphData);
|
: plotTitles.reduce(
|
||||||
|
|
||||||
if (Object.keys(graphData).length > 0) {
|
|
||||||
return graphData;
|
|
||||||
}
|
|
||||||
return plotTitles.reduce(
|
|
||||||
(acc, curr) => ({
|
(acc, curr) => ({
|
||||||
...acc,
|
...acc,
|
||||||
[curr]: {
|
[curr]: {
|
||||||
|
@ -198,45 +166,37 @@ const ScalarGraph = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderGraphs = () => {
|
const renderGraphs = () => {
|
||||||
|
if (checkedToggles) {
|
||||||
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
const coordinateTimeSeries = transformToGraphData(timeSeries);
|
||||||
console.log("coordinates", coordinateTimeSeries);
|
if (plotTitles.length === 0) {
|
||||||
const graphCoordinates: GraphCoordinates[] = Object.keys(
|
setPlotTitles(Object.keys(coordinateTimeSeries));
|
||||||
coordinateTimeSeries
|
}
|
||||||
)
|
return Object.keys(coordinateTimeSeries)
|
||||||
.filter((path) => {
|
.filter((path) => {
|
||||||
return checkedToggles?.[path];
|
return checkedToggles[path];
|
||||||
})
|
})
|
||||||
.map((path, i) => {
|
.map((path) => {
|
||||||
return {
|
const data = coordinateTimeSeries[path] ?? { x: [], y: [] };
|
||||||
...coordinateTimeSeries[path],
|
const isScalar = isNumeric(data.y[0]);
|
||||||
xaxis: "x",
|
if (!isScalar) {
|
||||||
yaxis: i === 0 ? "y" : "y" + (i + 1),
|
const barGraphData = transformToBarGraphData(data);
|
||||||
type: "scatter",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (checkedToggles && graphCoordinates.length > 0) {
|
|
||||||
const subplots = graphCoordinates.map((coordinate) => [
|
|
||||||
(coordinate?.xaxis || "") + (coordinate.yaxis || ""),
|
|
||||||
]);
|
|
||||||
return (
|
return (
|
||||||
<Plot
|
<Plot
|
||||||
data={graphCoordinates}
|
key={path}
|
||||||
|
data={barGraphData as Data[]}
|
||||||
layout={{
|
layout={{
|
||||||
title: "Graphs",
|
height: 500,
|
||||||
|
width: 1000,
|
||||||
|
title: path,
|
||||||
uirevision: uiRevision,
|
uirevision: uiRevision,
|
||||||
showlegend: false,
|
|
||||||
xaxis: {
|
xaxis: {
|
||||||
autorange: false,
|
autorange: false,
|
||||||
range: range,
|
range: range,
|
||||||
type: "date",
|
type: "date",
|
||||||
showticklabels: true,
|
|
||||||
},
|
},
|
||||||
grid: {
|
bargap: 0,
|
||||||
subplots: subplots as any,
|
barmode: "stack",
|
||||||
xside: "top",
|
barnorm: "percent",
|
||||||
ygap: 0.1,
|
|
||||||
},
|
|
||||||
height: graphCoordinates.length * 300,
|
|
||||||
}}
|
}}
|
||||||
config={{
|
config={{
|
||||||
modeBarButtonsToRemove: [
|
modeBarButtonsToRemove: [
|
||||||
|
@ -251,10 +211,7 @@ const ScalarGraph = () => {
|
||||||
const xaxisRange1 = params["xaxis.range[1]"];
|
const xaxisRange1 = params["xaxis.range[1]"];
|
||||||
|
|
||||||
if (xaxisRange0 && xaxisRange1) {
|
if (xaxisRange0 && xaxisRange1) {
|
||||||
setRange([
|
setRange([new Date(xaxisRange0), new Date(xaxisRange1)]);
|
||||||
new Date(xaxisRange0).getTime(),
|
|
||||||
new Date(xaxisRange1).getTime(),
|
|
||||||
]);
|
|
||||||
setUiRevision(Math.random());
|
setUiRevision(Math.random());
|
||||||
const times = createTimes(
|
const times = createTimes(
|
||||||
TimeRange.fromTimes(
|
TimeRange.fromTimes(
|
||||||
|
@ -263,7 +220,6 @@ const ScalarGraph = () => {
|
||||||
),
|
),
|
||||||
NUMBER_OF_NODES
|
NUMBER_OF_NODES
|
||||||
);
|
);
|
||||||
console.log("times", times);
|
|
||||||
cache.getSeries(times);
|
cache.getSeries(times);
|
||||||
times$.next(times);
|
times$.next(times);
|
||||||
}
|
}
|
||||||
|
@ -271,6 +227,58 @@ const ScalarGraph = () => {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return (
|
||||||
|
<Plot
|
||||||
|
key={path}
|
||||||
|
data={[
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
type: isScalar ? "scatter" : "bar",
|
||||||
|
mode: "lines+markers",
|
||||||
|
marker: { color: "red" },
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
layout={{
|
||||||
|
width: 1000,
|
||||||
|
height: 500,
|
||||||
|
title: path,
|
||||||
|
uirevision: uiRevision,
|
||||||
|
xaxis: {
|
||||||
|
autorange: false,
|
||||||
|
range: range,
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
config={{
|
||||||
|
modeBarButtonsToRemove: [
|
||||||
|
"lasso2d",
|
||||||
|
"select2d",
|
||||||
|
"pan2d",
|
||||||
|
"autoScale2d",
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
onRelayout={(params) => {
|
||||||
|
const xaxisRange0 = params["xaxis.range[0]"];
|
||||||
|
const xaxisRange1 = params["xaxis.range[1]"];
|
||||||
|
|
||||||
|
if (xaxisRange0 && xaxisRange1) {
|
||||||
|
setRange([new Date(xaxisRange0), new Date(xaxisRange1)]);
|
||||||
|
setUiRevision(Math.random());
|
||||||
|
const times = createTimes(
|
||||||
|
TimeRange.fromTimes(
|
||||||
|
UnixTime.fromDate(new Date(xaxisRange0)),
|
||||||
|
UnixTime.fromDate(new Date(xaxisRange1))
|
||||||
|
),
|
||||||
|
NUMBER_OF_NODES
|
||||||
|
);
|
||||||
|
cache.getSeries(times);
|
||||||
|
times$.next(times);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return <>{renderGraphs()}</>;
|
return <>{renderGraphs()}</>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,30 +1,18 @@
|
||||||
import { Datum, TypedArray } from "plotly.js";
|
import { Datum, TypedArray } from "plotly.js";
|
||||||
|
import {
|
||||||
export const mergeDeep = (...objects: any[]) => {
|
TreeElement,
|
||||||
const isObject = (obj: GraphCoordinates) => obj && typeof obj === "object";
|
ToggleElement,
|
||||||
return objects.reduce((prev, obj) => {
|
} from "../components/Installations/Log/CheckboxTree";
|
||||||
Object.keys(obj).forEach((key) => {
|
|
||||||
const pVal = prev[key];
|
|
||||||
const oVal = obj[key];
|
|
||||||
|
|
||||||
if (Array.isArray(pVal) && Array.isArray(oVal)) {
|
|
||||||
prev[key] = pVal.concat(...oVal);
|
|
||||||
} else if (isObject(pVal) && isObject(oVal)) {
|
|
||||||
prev[key] = mergeDeep(pVal, oVal);
|
|
||||||
} else {
|
|
||||||
prev[key] = oVal;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return prev;
|
|
||||||
}, {} as GraphData);
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface GraphCoordinates {
|
export interface GraphCoordinates {
|
||||||
x: Datum[] | Datum[][] | TypedArray;
|
x: Datum[] | Datum[][] | TypedArray;
|
||||||
y: Datum[] | Datum[][] | TypedArray;
|
y: Datum[] | Datum[][] | TypedArray;
|
||||||
xaxis?: string;
|
xaxis?: string;
|
||||||
yaxis?: string;
|
yaxis?: string;
|
||||||
|
barmode?: string;
|
||||||
|
marker?: { color: string };
|
||||||
|
type?: string;
|
||||||
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GraphData {
|
export interface GraphData {
|
||||||
|
@ -41,19 +29,104 @@ export const parseCsv = (text: string) => {
|
||||||
}
|
}
|
||||||
return l.split(";");
|
return l.split(";");
|
||||||
});
|
});
|
||||||
console.log("text", y);
|
|
||||||
|
|
||||||
/* .filter((fields) => !isNaN(parseFloat(fields[1])));
|
|
||||||
*/
|
|
||||||
const x = y
|
const x = y
|
||||||
.map((fields) => {
|
.map((fields) => {
|
||||||
if (typeof fields[1] === "string") {
|
if (typeof fields[1] === "string") {
|
||||||
console.log("if inside", fields, { [fields[0]]: fields[1] });
|
|
||||||
return { [fields[0]]: fields[1] };
|
return { [fields[0]]: fields[1] };
|
||||||
}
|
}
|
||||||
console.log("if outside", fields, { [fields[0]]: parseFloat(fields[1]) });
|
|
||||||
return { [fields[0]]: parseFloat(fields[1]) };
|
return { [fields[0]]: parseFloat(fields[1]) };
|
||||||
})
|
})
|
||||||
.reduce((acc, current) => ({ ...acc, ...current }), {} as any);
|
.reduce((acc, current) => ({ ...acc, ...current }), {} as any);
|
||||||
return x;
|
return x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const flattenToggles = (toggles: TreeElement[]): ToggleElement => {
|
||||||
|
return toggles.reduce((acc, current) => {
|
||||||
|
if (current.children.length > 0) {
|
||||||
|
acc[current.id] = false;
|
||||||
|
return { ...acc, ...flattenToggles(current.children) };
|
||||||
|
}
|
||||||
|
acc[current.id] = false;
|
||||||
|
return acc;
|
||||||
|
}, {} as ToggleElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const insertTreeElements = (
|
||||||
|
children: TreeElement[] = [],
|
||||||
|
[head, ...tail]: string[]
|
||||||
|
): TreeElement[] => {
|
||||||
|
let child = children.find((child) => child.name === head);
|
||||||
|
|
||||||
|
if (!child) {
|
||||||
|
children.push(
|
||||||
|
(child = {
|
||||||
|
id: head,
|
||||||
|
name: head,
|
||||||
|
children: [],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (tail.length > 0) {
|
||||||
|
insertTreeElements(child.children, tail);
|
||||||
|
}
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isText = (data: any): data is string => {
|
||||||
|
return typeof data === "string";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const flattenBarGraphData = (arr: any): GraphCoordinates[] => {
|
||||||
|
return arr.reduce((flat: any, toFlatten: any) => {
|
||||||
|
return flat.concat(
|
||||||
|
Array.isArray(toFlatten) ? flattenBarGraphData(toFlatten) : toFlatten
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stringToColor = (str: string) => {
|
||||||
|
if (str.length === 0) {
|
||||||
|
return "#FFFFFF";
|
||||||
|
}
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
let colour = "#";
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
const value = (hash >> (i * 8)) & 0xff;
|
||||||
|
colour += ("02" + value.toString(16)).substr(-2);
|
||||||
|
}
|
||||||
|
return colour;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isNumeric = (value: any) => {
|
||||||
|
return !isNaN(value) && !isNaN(parseFloat(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const transformToBarGraphData = (data: GraphCoordinates) => {
|
||||||
|
let names: string[] = [];
|
||||||
|
const barGraphData = data.y.map((text, i) => {
|
||||||
|
if (isText(text)) {
|
||||||
|
const splitText = text.split(",");
|
||||||
|
return splitText.map((split) => {
|
||||||
|
const foundName = !!names.find((value) => value === split);
|
||||||
|
if (!foundName) {
|
||||||
|
names.push(split);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
x: [data.x[i]],
|
||||||
|
y: [1 / splitText.length],
|
||||||
|
barmode: "stack",
|
||||||
|
marker: { color: stringToColor(split) },
|
||||||
|
type: "bar",
|
||||||
|
name: split,
|
||||||
|
showlegend: !foundName,
|
||||||
|
} as any;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [] as any;
|
||||||
|
});
|
||||||
|
return flattenBarGraphData(barGraphData);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in New Issue