Skip to content

Commit

Permalink
Merge pull request #69 from uchicago-dsi/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
toddnief authored Aug 23, 2024
2 parents 60d5a3b + 55ee1b5 commit fec7329
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 118 deletions.
67 changes: 26 additions & 41 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,33 @@
"build": {
"dockerfile": "../Dockerfile",
"context": "..",
"args": {},
"args": {}
},
// Set *default* container specific settings.json values on container create.
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.profiles.linux": {
"bash": {
"path": "/bin/bash"
}
},
"python.defaultInterpreterPath": "/usr/local/bin/python3",
"python.languageServer": "Pylance",
"python.formatting.provider": "black",
"[python]": {
"diffEditor.ignoreTrimWhitespace": false,
"editor.formatOnSave": true,
"editor.wordBasedSuggestions": "matchingDocuments",
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"isort.args": [
"--settings-path", "${workspaceFolder}/setup.cfg"
],
"flake8.args": [
"--config", "${workspaceFolder}/setup.cfg"
]
},
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.flake8",
"ms-python.black-formatter",
"ms-python.isort",
"ms-vscode-remote.remote-containers",
"ms-toolsai.jupyter",
"ms-toolsai.jupyter-renderers"
]
}
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.profiles.linux": {
"bash": {
"path": "/bin/bash"
}
},
"python.defaultInterpreterPath": "/usr/local/bin/python3",
"python.languageServer": "None", // Prevent Pylance warnings - using Ruff
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"extensions": [
"ms-python.debugpy",
"charliermarsh.ruff",
"ms-vscode-remote.remote-containers",
"ms-toolsai.jupyter",
"ms-toolsai.jupyter-renderers"
]
}
},
// Add the IDs of extensions you want installed when the container is created.
"features": {
"github-cli": "latest"
Expand All @@ -70,4 +55,4 @@
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"workspaceMount": "source=${localWorkspaceFolder},target=/project,type=bind",
"workspaceFolder": "/project"
}
}
2 changes: 1 addition & 1 deletion dashboard-react/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ body {
}

h2 {
@apply text-2xl font-bold uppercase text-center text-primary underline;
@apply text-3xl font-bold uppercase text-center underline;
}

h3 {
Expand Down
46 changes: 46 additions & 0 deletions dashboard-react/app/operating-conditions/page.js
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>
);
}
2 changes: 1 addition & 1 deletion dashboard-react/components/DashboardDisplayControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function DashboardDisplayControls() {

return (
<>
<div className="flex justify-center">
<div className="flex justify-center mb-4">
<h2>Display Options</h2>
</div>
<RadioSingleSelect
Expand Down
175 changes: 175 additions & 0 deletions dashboard-react/components/OperatingConditionsDashboard.js
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,
}}
/>
)}
</>
);
}
Loading

0 comments on commit fec7329

Please sign in to comment.