added tons of files, got the bin page to render
This commit is contained in:
parent
a027b8a96f
commit
9bbbf0940e
34 changed files with 10492 additions and 19 deletions
152
src/charts/BarGraph.tsx
Normal file
152
src/charts/BarGraph.tsx
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
// import { useTheme } from "@mui/material";
|
||||
import { useTheme } from "@mui/material";
|
||||
import moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
LabelList,
|
||||
ReferenceArea,
|
||||
ResponsiveContainer,
|
||||
XAxis,
|
||||
YAxis
|
||||
} from "recharts";
|
||||
|
||||
export interface BarData {
|
||||
timestamp: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface RefArea {
|
||||
x1: number;
|
||||
x2: number;
|
||||
y1: number;
|
||||
y2: number;
|
||||
fill: string;
|
||||
}
|
||||
|
||||
export interface AxisLabel {
|
||||
value: string;
|
||||
//must be a valid label position, for a list of possible position see Recharts documentation
|
||||
position: string;
|
||||
angle?: number;
|
||||
offset?: number;
|
||||
fill?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
data: BarData[];
|
||||
barColour: string;
|
||||
refAreas?: RefArea[];
|
||||
yMin?: number;
|
||||
yMax?: number;
|
||||
graphLegend?: JSX.Element;
|
||||
labels?: boolean;
|
||||
labelSize?: number;
|
||||
useGradient?: boolean;
|
||||
labelColour?: string;
|
||||
customHeight?: number | string;
|
||||
xAxisType?: "number" | "category";
|
||||
barGap?: number | string;
|
||||
barSize?: number;
|
||||
yLabel?: AxisLabel;
|
||||
xLabel?: AxisLabel;
|
||||
}
|
||||
|
||||
export default function BarGraph(props: Props) {
|
||||
const now = moment();
|
||||
const theme = useTheme();
|
||||
const {
|
||||
data,
|
||||
barColour,
|
||||
refAreas,
|
||||
yMin,
|
||||
yMax,
|
||||
graphLegend,
|
||||
labels,
|
||||
labelSize,
|
||||
useGradient,
|
||||
labelColour,
|
||||
customHeight,
|
||||
xAxisType,
|
||||
barGap,
|
||||
barSize,
|
||||
yLabel,
|
||||
xLabel
|
||||
} = props;
|
||||
const [referenceAreas, setReferenceAreas] = useState<JSX.Element[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let ras: JSX.Element[] = [];
|
||||
if (refAreas && refAreas.length > 0) {
|
||||
refAreas.forEach((ra, i) => {
|
||||
ras.push(
|
||||
<ReferenceArea
|
||||
key={"area#" + i}
|
||||
x1={ra.x1}
|
||||
x2={ra.x2}
|
||||
y1={ra.y1}
|
||||
y2={ra.y2}
|
||||
fill={ra.fill}
|
||||
fillOpacity={1}
|
||||
ifOverflow="extendDomain"
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
setReferenceAreas(ras);
|
||||
}, [refAreas]);
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width={"100%"} height={customHeight ?? 350}>
|
||||
<BarChart data={data} barGap={barGap}>
|
||||
{graphLegend}
|
||||
<YAxis
|
||||
domain={[yMin ?? "dataMin", yMax ?? "auto"]}
|
||||
interval="preserveStartEnd"
|
||||
scale={"linear"}
|
||||
label={yLabel}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey={"timestamp"}
|
||||
domain={["dataMin", "dataMax"]}
|
||||
name="Time"
|
||||
scale={xAxisType === "number" ? "time" : "auto"}
|
||||
type={xAxisType}
|
||||
label={xLabel}
|
||||
tickFormatter={tick => {
|
||||
if (!isNaN(tick)) {
|
||||
let t = moment(tick);
|
||||
return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD");
|
||||
} else {
|
||||
return tick;
|
||||
}
|
||||
}}
|
||||
tick={{ fill: theme.palette.text.primary }}
|
||||
stroke={theme.palette.divider}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient id="gradientColour" x1="0" y1="1" x2="0" y2="0">
|
||||
<stop offset="0%" stopColor={barColour} stopOpacity="0%" />
|
||||
<stop offset="100%" stopColor={barColour} stopOpacity="90%" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Bar
|
||||
dataKey={"value"}
|
||||
fill={useGradient ? "url(#gradientColour)" : barColour}
|
||||
barSize={barSize}>
|
||||
{labels && (
|
||||
<LabelList
|
||||
fill={labelColour}
|
||||
dataKey="value"
|
||||
position="center"
|
||||
style={{ fontWeight: 750, fontSize: labelSize }}
|
||||
/>
|
||||
)}
|
||||
</Bar>
|
||||
{referenceAreas}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
176
src/charts/TrendingChart.tsx
Normal file
176
src/charts/TrendingChart.tsx
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import { useTheme } from "@mui/material";
|
||||
import { teal, orange } from "@mui/material/colors";
|
||||
import moment from "moment";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Area,
|
||||
ComposedChart,
|
||||
Legend,
|
||||
Line,
|
||||
ReferenceArea,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
XAxis,
|
||||
YAxis
|
||||
} from "recharts";
|
||||
import MaterialChartTooltip from "./MaterialChartTooltip";
|
||||
|
||||
export interface DataPoint {
|
||||
timestamp: number;
|
||||
moisture: [number, number];
|
||||
}
|
||||
|
||||
export interface TrendPoint {
|
||||
timestamp: number;
|
||||
trend: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
data: DataPoint[];
|
||||
trend: TrendPoint[];
|
||||
newXDomain?: number[] | string[];
|
||||
multiGraphZoom?: (domain: number[] | string[]) => void;
|
||||
multiGraphZoomOut?: boolean;
|
||||
}
|
||||
|
||||
interface ComposedData {
|
||||
timestamp: number;
|
||||
trend?: number;
|
||||
moisture?: [number, number];
|
||||
}
|
||||
|
||||
export default function TrendingChart(props: Props) {
|
||||
const { data, trend, newXDomain, multiGraphZoom } = props;
|
||||
const theme = useTheme();
|
||||
const now = moment();
|
||||
const [composedData, setComposedData] = useState<ComposedData[]>([]);
|
||||
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
||||
const [refLeft, setRefLeft] = useState<number | undefined>();
|
||||
const [refRight, setRefRight] = useState<number | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (newXDomain) {
|
||||
setXDomain(newXDomain);
|
||||
}
|
||||
}, [newXDomain]);
|
||||
|
||||
useEffect(() => {
|
||||
let newData: ComposedData[] = [];
|
||||
data.forEach(d => {
|
||||
newData.push({
|
||||
timestamp: d.timestamp,
|
||||
moisture: d.moisture
|
||||
});
|
||||
});
|
||||
trend.forEach(t => {
|
||||
newData.push({
|
||||
timestamp: t.timestamp,
|
||||
trend: Math.round(t.trend * 100) / 100
|
||||
});
|
||||
});
|
||||
|
||||
setComposedData([...newData]);
|
||||
}, [data, trend]);
|
||||
|
||||
const zoom = () => {
|
||||
let newDomain: number[] | string[] = ["dataMin", "dataMax"];
|
||||
if (refLeft && refRight && refLeft !== refRight) {
|
||||
refLeft < refRight ? (newDomain = [refLeft, refRight]) : (newDomain = [refRight, refLeft]);
|
||||
setRefLeft(undefined);
|
||||
setRefRight(undefined);
|
||||
if (multiGraphZoom) {
|
||||
multiGraphZoom(newDomain);
|
||||
} else {
|
||||
setXDomain(newDomain);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//just commenting this out as this charts zoom will likely never be controlled from inside
|
||||
// const zoomOut = () => {
|
||||
// setXDomain(["dataMin", "dataMax"]);
|
||||
// };
|
||||
|
||||
if (composedData.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ResponsiveContainer width={"100%"} height={270}>
|
||||
<ComposedChart
|
||||
data={composedData}
|
||||
onMouseDown={(e: any) => {
|
||||
if (e) {
|
||||
setRefLeft(e.activeLabel);
|
||||
}
|
||||
}}
|
||||
onMouseMove={(e: any) => {
|
||||
if (e) {
|
||||
setRefRight(e.activeLabel);
|
||||
}
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
setRefLeft(undefined);
|
||||
setRefRight(undefined);
|
||||
zoom();
|
||||
}}>
|
||||
<Legend
|
||||
verticalAlign="top"
|
||||
payload={[
|
||||
{ value: "Grain Moisture", type: "square", id: "gMoist", color: teal[500] },
|
||||
{ value: "Plenum Moisture", type: "square", id: "pMoist", color: orange[500] }
|
||||
]}
|
||||
/>
|
||||
<Tooltip
|
||||
animationEasing="ease-out"
|
||||
cursor={{ fill: theme.palette.text.primary, opacity: "0.15" }}
|
||||
labelFormatter={timestamp => moment(timestamp).format("lll")}
|
||||
content={(props: TooltipProps<any, any>) => (
|
||||
<MaterialChartTooltip {...props} valueFormatter={val => `${val}`} />
|
||||
)}
|
||||
/>
|
||||
<XAxis
|
||||
allowDataOverflow
|
||||
dataKey="timestamp"
|
||||
domain={xDomain}
|
||||
name="Time"
|
||||
tickFormatter={timestamp => {
|
||||
let t = moment(timestamp);
|
||||
return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD");
|
||||
}}
|
||||
scale="time"
|
||||
type="number"
|
||||
tick={{ fill: theme.palette.text.primary }}
|
||||
stroke={theme.palette.divider}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<YAxis
|
||||
domain={["auto", "auto"]}
|
||||
tickFormatter={moisture => {
|
||||
let val = moisture + "%";
|
||||
return val;
|
||||
}}
|
||||
// label={{
|
||||
// value: "Moisture (%)",
|
||||
// angle: -90,
|
||||
// position: "insideLeft",
|
||||
// fill: theme.palette.text.primary
|
||||
// }}
|
||||
tick={{ fill: theme.palette.text.primary }}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={"moisture"}
|
||||
name="Moisture"
|
||||
stroke={"transparent"}
|
||||
fill={teal[500]}
|
||||
/>
|
||||
{refLeft && refRight ? (
|
||||
<ReferenceArea x1={refLeft} x2={refRight} strokeOpacity={0.3} />
|
||||
) : null}
|
||||
<Line dataKey="trend" stroke={orange[500]} strokeWidth={2} dot={false} />
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
136
src/charts/WaterLevelChart.tsx
Normal file
136
src/charts/WaterLevelChart.tsx
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import { useTheme } from "@mui/material";
|
||||
import { blue } from "@mui/material/colors";
|
||||
import moment from "moment";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Area,
|
||||
AreaChart,
|
||||
ReferenceArea,
|
||||
ReferenceLine,
|
||||
ResponsiveContainer,
|
||||
Tooltip,
|
||||
TooltipProps,
|
||||
XAxis,
|
||||
YAxis
|
||||
} from "recharts";
|
||||
import MaterialChartTooltip from "./MaterialChartTooltip";
|
||||
|
||||
export interface DataPoint {
|
||||
timestamp: number;
|
||||
liters: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
data: DataPoint[];
|
||||
reference: number;
|
||||
newXDomain?: number[] | string[];
|
||||
multiGraphZoom?: (domain: number[] | string[]) => void;
|
||||
multiGraphZoomOut?: boolean;
|
||||
}
|
||||
|
||||
export default function WaterLevelChart(props: Props) {
|
||||
const { data, reference, newXDomain, multiGraphZoom } = props;
|
||||
const theme = useTheme();
|
||||
const now = moment();
|
||||
const [xDomain, setXDomain] = useState<string[] | number[]>(["dataMin", "dataMax"]);
|
||||
const [refLeft, setRefLeft] = useState<number | undefined>();
|
||||
const [refRight, setRefRight] = useState<number | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (newXDomain) {
|
||||
setXDomain(newXDomain);
|
||||
}
|
||||
}, [newXDomain]);
|
||||
|
||||
const zoom = () => {
|
||||
let newDomain: number[] | string[] = ["dataMin", "dataMax"];
|
||||
if (refLeft && refRight && refLeft !== refRight) {
|
||||
refLeft < refRight ? (newDomain = [refLeft, refRight]) : (newDomain = [refRight, refLeft]);
|
||||
setRefLeft(undefined);
|
||||
setRefRight(undefined);
|
||||
if (multiGraphZoom) {
|
||||
multiGraphZoom(newDomain);
|
||||
} else {
|
||||
setXDomain(newDomain);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//just commenting this out as this charts zoom will likely never be controlled from inside
|
||||
// const zoomOut = () => {
|
||||
// setXDomain(["dataMin", "dataMax"]);
|
||||
// };
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ResponsiveContainer width={"100%"} height={270}>
|
||||
<AreaChart
|
||||
data={data}
|
||||
onMouseDown={(e: any) => {
|
||||
if (e) {
|
||||
setRefLeft(e.activeLabel);
|
||||
}
|
||||
}}
|
||||
onMouseMove={(e: any) => {
|
||||
if (e) {
|
||||
setRefRight(e.activeLabel);
|
||||
}
|
||||
}}
|
||||
onMouseUp={() => {
|
||||
setRefLeft(undefined);
|
||||
setRefRight(undefined);
|
||||
zoom();
|
||||
}}>
|
||||
<Tooltip
|
||||
animationEasing="ease-out"
|
||||
cursor={{ fill: theme.palette.text.primary, opacity: "0.15" }}
|
||||
labelFormatter={timestamp => moment(timestamp).format("lll")}
|
||||
content={(props: TooltipProps<any, any>) => (
|
||||
<MaterialChartTooltip {...props} valueFormatter={liters => `${liters}`} />
|
||||
)}
|
||||
/>
|
||||
<XAxis
|
||||
allowDataOverflow
|
||||
dataKey="timestamp"
|
||||
domain={xDomain}
|
||||
name="Time"
|
||||
tickFormatter={timestamp => {
|
||||
let t = moment(timestamp);
|
||||
return now.isSame(t, "day") ? t.format("LT") : t.format("MMM DD");
|
||||
}}
|
||||
scale="time"
|
||||
type="number"
|
||||
tick={{ fill: theme.palette.text.primary }}
|
||||
stroke={theme.palette.divider}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<YAxis
|
||||
tickFormatter={liters => {
|
||||
let val = liters / 1000 + "kL";
|
||||
return val;
|
||||
}}
|
||||
type="number"
|
||||
// label={{
|
||||
// value: "Water Content",
|
||||
// angle: -90,
|
||||
// position: "insideLeft",
|
||||
// fill: theme.palette.text.primary
|
||||
// }}
|
||||
domain={["auto", "auto"]}
|
||||
tick={{ fill: theme.palette.text.primary }}
|
||||
/>
|
||||
<Area dataKey={"liters"} fill={blue[500]} />
|
||||
<ReferenceLine
|
||||
ifOverflow="extendDomain"
|
||||
y={reference}
|
||||
stroke="red"
|
||||
strokeDasharray={"5 2"}
|
||||
/>
|
||||
{refLeft && refRight ? (
|
||||
<ReferenceArea x1={refLeft} x2={refRight} strokeOpacity={0.3} />
|
||||
) : null}
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue