-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #69 from uchicago-dsi/dev
Dev
- Loading branch information
Showing
7 changed files
with
379 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
"use client"; | ||
import React, { useEffect } from "react"; | ||
import state from "@/lib/state"; | ||
import dynamic from "next/dynamic"; | ||
import DashboardControls from "@/components/DashboardControls"; | ||
|
||
const OperatingConditionsDashboard = dynamic( | ||
() => import("@/components/OperatingConditionsDashboard"), | ||
{ | ||
ssr: false, | ||
} | ||
); | ||
|
||
export default function OperatingConditions() { | ||
// Document this better — kind of confusing cuz this is what gets the options for the menus | ||
// TODO: Is there a way to do this only on first load in state.js with Valtio? | ||
useEffect(() => { | ||
const fetchOptions = async () => { | ||
const response = await fetch("/api/options"); | ||
const result = await response.json(); | ||
Object.keys(result).forEach((key) => { | ||
state.setOptions(key, result[key]); | ||
}); | ||
}; | ||
fetchOptions(); | ||
}, []); | ||
|
||
return ( | ||
<main className="flex flex-col items-start"> | ||
<div className="block lg:hidden p-2 h-[100vh] flex items-center align-center justify-center"> | ||
Please use a device that is at least 1280 pixels wide to view the | ||
disintegration dashboard. | ||
</div> | ||
<div className="hidden lg:block h-[1300px] w-[1280px] overflow-hidden"> | ||
<div className="h-[600px] mx-auto mb-3"> | ||
<OperatingConditionsDashboard /> | ||
</div> | ||
{/* | ||
<div className="h-[700px] mx-auto w-full"> | ||
<DashboardControls /> | ||
</div> | ||
*/} | ||
</div> | ||
</main> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
175 changes: 175 additions & 0 deletions
175
dashboard-react/components/OperatingConditionsDashboard.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
"use client"; | ||
import React, { useState, useEffect } from "react"; | ||
import Plot from "react-plotly.js"; | ||
import { csv } from "d3-fetch"; | ||
|
||
export default function OperatingConditionsDashboard({ | ||
maxDays = 45, | ||
windowSize = 10, | ||
}) { | ||
// Add windowSize as a prop | ||
const [dataLoaded, setDataLoaded] = useState(false); | ||
const [plotData, setPlotData] = useState([]); | ||
const [errorMessage, setErrorMessage] = useState(""); | ||
|
||
useEffect(() => { | ||
csv("/data/temperature_data.csv") | ||
.then((data) => { | ||
const formattedData = []; | ||
const days = data.map((d) => d["Day #"]); | ||
|
||
const trialCount = {}; // Reset trial count each time data is processed | ||
|
||
Object.keys(data[0]).forEach((column) => { | ||
if (column !== "Day #") { | ||
let yData = data.map((d) => parseFloat(d[column]) || null); | ||
yData = interpolateData(yData); // Perform interpolation | ||
yData = movingAverage(yData, windowSize); // Smooth using moving average | ||
|
||
const trialName = mapTrialName(column, trialCount); // Pass trialCount to mapTrialName | ||
|
||
formattedData.push({ | ||
x: days, | ||
y: yData, | ||
mode: "lines", | ||
name: trialName, // Use the mapped trial name | ||
}); | ||
} | ||
}); | ||
|
||
setPlotData(formattedData); | ||
setDataLoaded(true); | ||
}) | ||
.catch((error) => { | ||
console.error("Error loading CSV data:", error); | ||
setErrorMessage("Failed to load data."); | ||
}); | ||
}, [windowSize]); | ||
|
||
const mapTrialName = (trialName, trialCount) => { | ||
const mappings = { | ||
IV: "In-Vessel", | ||
CASP: "Covered Aerated Static Pile", | ||
WR: "Windrow", | ||
EASP: "Extended Aerated Static Pile", | ||
ASP: "Aerated Static Pile", | ||
AD: "Anaerobic Digestion", | ||
}; | ||
|
||
// Extract the prefix (e.g., IV, CASP, etc.) | ||
const prefix = trialName.match(/^[A-Z]+/)[0]; | ||
|
||
// Get the mapped name for the prefix | ||
const mappedName = mappings[prefix]; | ||
|
||
if (mappedName) { | ||
// Initialize the count for this trial type if it doesn't exist | ||
if (!trialCount[mappedName]) { | ||
trialCount[mappedName] = 0; | ||
} | ||
// Increment the count for this trial type | ||
trialCount[mappedName] += 1; | ||
|
||
// Return the formatted name with the count | ||
return `${mappedName} #${trialCount[mappedName]}`; | ||
} | ||
|
||
return trialName; // Return the original trial name if the prefix is not recognized | ||
}; | ||
|
||
// Linear interpolation function | ||
function interpolateData(yData) { | ||
let lastValidIndex = null; | ||
|
||
for (let i = 0; i < yData.length; i++) { | ||
if (yData[i] === null) { | ||
// Find the next valid index | ||
const nextValidIndex = yData.slice(i).findIndex((v) => v !== null) + i; | ||
|
||
if (lastValidIndex !== null && nextValidIndex < yData.length) { | ||
// Interpolate between the last valid and next valid index | ||
const slope = | ||
(yData[nextValidIndex] - yData[lastValidIndex]) / | ||
(nextValidIndex - lastValidIndex); | ||
yData[i] = yData[lastValidIndex] + slope * (i - lastValidIndex); | ||
} | ||
} else { | ||
lastValidIndex = i; | ||
} | ||
} | ||
|
||
return yData; | ||
} | ||
|
||
// Moving average function | ||
function movingAverage(data, windowSize) { | ||
return data.map((value, idx, arr) => { | ||
// Ignore null values | ||
if (value === null) return null; | ||
|
||
const start = Math.max(0, idx - Math.floor(windowSize / 2)); | ||
const end = Math.min(arr.length, idx + Math.ceil(windowSize / 2)); | ||
const window = arr.slice(start, end); | ||
const validNumbers = window.filter((n) => n !== null); | ||
|
||
if (validNumbers.length === 0) return null; | ||
|
||
const sum = validNumbers.reduce((acc, num) => acc + num, 0); | ||
return sum / validNumbers.length; | ||
}); | ||
} | ||
|
||
const yAxisTitle = "Temperature"; | ||
|
||
const title = "Temperature Over Time"; | ||
|
||
const yMax = | ||
plotData.length > 0 | ||
? Math.max(...plotData.flatMap((d) => d.y.map((y) => y + 0.05)), 1.05) | ||
: 1.05; | ||
|
||
const xTickAngle = plotData.length > 6 ? 90 : 0; | ||
|
||
return ( | ||
<> | ||
{errorMessage ? ( | ||
<div className="flex items-center justify-center h-full mx-[200px]"> | ||
<p>{errorMessage}</p> | ||
</div> | ||
) : ( | ||
<Plot | ||
data={plotData} | ||
layout={{ | ||
width: 1280, | ||
height: 600, | ||
title: { | ||
text: `<b>${title}</b>`, | ||
x: 0.5, | ||
xanchor: "center", | ||
yanchor: "top", | ||
}, | ||
showlegend: true, | ||
yaxis: { | ||
title: { | ||
text: `<b>${yAxisTitle}</b>`, | ||
}, | ||
range: [0, yMax], | ||
linewidth: 2, // Set y-axis line thickness | ||
}, | ||
xaxis: { | ||
tickangle: xTickAngle, | ||
ticklen: 10, | ||
automargin: true, | ||
range: [0, maxDays], // Cap x-axis at maxDays | ||
linewidth: 2, // Set x-axis line thickness | ||
}, | ||
hovermode: "x", | ||
}} | ||
config={{ | ||
displayModeBar: false, | ||
}} | ||
/> | ||
)} | ||
</> | ||
); | ||
} |
Oops, something went wrong.