add working bar charts, before restructureing of code

This commit is contained in:
Sina Blattmann 2023-05-15 13:19:08 +02:00
parent aef9ee4907
commit 0533a86b11
2 changed files with 254 additions and 173 deletions

View File

@ -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,120 +130,154 @@ 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], }
}, transformedObject[key].x.push(
}; new Date(item.time.ticks * 1000).toISOString()
} );
return pathAcc; transformedObject[key].y.push(item.value?.[key]);
}, });
}
if (plotTitles.length === 0) {
setPlotTitles(Object.keys(transformedObject));
}
});
return Object.keys(transformedObject).length > 0
? transformedObject
: plotTitles.reduce(
(acc, curr) => ({
...acc,
[curr]: {
x: [],
y: [],
},
}),
{} as GraphData {} as GraphData
); );
if (plotTitles.length === 0) {
setPlotTitles(Object.keys(curr.value));
}
return mergeDeep(acc, timeStampObj);
}
return acc;
}, {} as GraphData);
if (Object.keys(graphData).length > 0) {
return graphData;
}
return plotTitles.reduce(
(acc, curr) => ({
...acc,
[curr]: {
x: [],
y: [],
},
}),
{} as GraphData
);
}; };
const renderGraphs = () => { const renderGraphs = () => {
const coordinateTimeSeries = transformToGraphData(timeSeries); if (checkedToggles) {
console.log("coordinates", coordinateTimeSeries); const coordinateTimeSeries = transformToGraphData(timeSeries);
const graphCoordinates: GraphCoordinates[] = Object.keys( if (plotTitles.length === 0) {
coordinateTimeSeries setPlotTitles(Object.keys(coordinateTimeSeries));
) }
.filter((path) => { return Object.keys(coordinateTimeSeries)
return checkedToggles?.[path]; .filter((path) => {
}) return checkedToggles[path];
.map((path, i) => { })
return { .map((path) => {
...coordinateTimeSeries[path], const data = coordinateTimeSeries[path] ?? { x: [], y: [] };
xaxis: "x", const isScalar = isNumeric(data.y[0]);
yaxis: i === 0 ? "y" : "y" + (i + 1), if (!isScalar) {
type: "scatter", const barGraphData = transformToBarGraphData(data);
}; return (
}); <Plot
if (checkedToggles && graphCoordinates.length > 0) { key={path}
const subplots = graphCoordinates.map((coordinate) => [ data={barGraphData as Data[]}
(coordinate?.xaxis || "") + (coordinate.yaxis || ""), layout={{
]); height: 500,
return ( width: 1000,
<Plot title: path,
data={graphCoordinates} uirevision: uiRevision,
layout={{ xaxis: {
title: "Graphs", autorange: false,
uirevision: uiRevision, range: range,
showlegend: false, type: "date",
xaxis: { },
autorange: false, bargap: 0,
range: range, barmode: "stack",
type: "date", barnorm: "percent",
showticklabels: true, }}
}, config={{
grid: { modeBarButtonsToRemove: [
subplots: subplots as any, "lasso2d",
xside: "top", "select2d",
ygap: 0.1, "pan2d",
}, "autoScale2d",
height: graphCoordinates.length * 300, ],
}} }}
config={{ onRelayout={(params) => {
modeBarButtonsToRemove: [ const xaxisRange0 = params["xaxis.range[0]"];
"lasso2d", const xaxisRange1 = params["xaxis.range[1]"];
"select2d",
"pan2d",
"autoScale2d",
],
}}
onRelayout={(params) => {
const xaxisRange0 = params["xaxis.range[0]"];
const xaxisRange1 = params["xaxis.range[1]"];
if (xaxisRange0 && xaxisRange1) { if (xaxisRange0 && xaxisRange1) {
setRange([ setRange([new Date(xaxisRange0), new Date(xaxisRange1)]);
new Date(xaxisRange0).getTime(), setUiRevision(Math.random());
new Date(xaxisRange1).getTime(), const times = createTimes(
]); TimeRange.fromTimes(
setUiRevision(Math.random()); UnixTime.fromDate(new Date(xaxisRange0)),
const times = createTimes( UnixTime.fromDate(new Date(xaxisRange1))
TimeRange.fromTimes( ),
UnixTime.fromDate(new Date(xaxisRange0)), NUMBER_OF_NODES
UnixTime.fromDate(new Date(xaxisRange1)) );
), cache.getSeries(times);
NUMBER_OF_NODES times$.next(times);
); }
console.log("times", times); }}
cache.getSeries(times); />
times$.next(times); );
} }
}} 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()}</>;

View File

@ -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);
};