added tons of files, got the bin page to render

This commit is contained in:
Carter 2025-03-12 12:41:20 -06:00
parent a027b8a96f
commit 9bbbf0940e
34 changed files with 10492 additions and 19 deletions

152
src/charts/BarGraph.tsx Normal file
View 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>
);
}

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

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