Skip to content

Commit

Permalink
Merge pull request #68 from uchicago-dsi/operating-conditions-dash
Browse files Browse the repository at this point in the history
Operating conditions dash
  • Loading branch information
toddnief authored Aug 23, 2024
2 parents 00998d3 + f89cf1e commit 55ee1b5
Show file tree
Hide file tree
Showing 4 changed files with 330 additions and 8 deletions.
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>
);
}
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,
}}
/>
)}
</>
);
}
98 changes: 98 additions & 0 deletions dashboard-react/public/data/temperature_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
Day #,WR004-01,CASP005-01,EASP001-01,IV002-01,EASP002-01,CASP006-01,CASP004-02,ASP001-01,EASP003-01,WR005-01
1,112.8,128.8,92.16333333333334,103.625,89.16666666666667,103.0,172.352,127.8,120.4,146.5
2,117.0,137.6,133.09,118.25,114.66666666666667,,169.172,133.8,116.2,136.0
3,125.2,140.8,116.84666666666668,117.875,129.66666666666666,109.0,164.956,136.4,124.2,141.0
4,128.0,,156.8,118.42857142857143,,,161.138,,,142.0
5,,,161.97,118.0,,,144.674,,,142.33333333333334
6,,142.6,140.29999999999998,117.66666666666667,143.66666666666666,137.33333333333334,137.366,142.0,165.4,
7,134.0,143.4,162.1,118.85714285714286,148.66666666666666,138.0,132.35000000000002,144.4,162.0,
8,135.8,144.6,166.95,119.28571428571429,149.66666666666666,141.33333333333334,157.474,146.4,167.2,108.0
9,136.4,131.2,170.01,121.88888888888889,158.66666666666666,146.66666666666666,155.116,148.0,170.0,107.33333333333333
10,139.2,156.6,143.47375,120.14285714285714,162.0,151.33333333333334,156.818,148.2,170.4,109.33333333333333
11,135.8,,169.03,119.57142857142857,,,128.574,,,110.33333333333333
12,,,169.77,119.14285714285714,,,115.68199999999999,,,108.0
13,,163.0,140.04375,118.57142857142857,160.0,,122.04,140.2,165.4,109.0
14,138.2,164.6,171.12,117.85714285714286,161.66666666666666,133.0,127.976,141.0,164.8,
15,135.8,166.0,134.94375,117.57142857142857,162.66666666666666,132.33333333333334,118.00199999999998,142.6,166.6,126.0
16,136.6,166.2,172.48,117.57142857142857,161.66666666666666,136.66666666666666,127.91400000000002,,165.2,129.0
17,135.8,168.2,138.24625,119.0,161.66666666666666,,117.50600000000001,142.2,160.2,144.83333333333334
18,136.6,,167.4,117.28571428571429,,,126.46400000000001,,,133.66666666666666
19,,,166.56,118.14285714285714,,,117.556,,,148.16666666666666
20,,,127.5975,117.71428571428571,160.83333333333334,133.33333333333334,122.648,142.6,153.8,
21,136.4,150.6,170.6,118.57142857142857,159.0,137.5,131.92999999999998,155.2,,
22,137.2,150.8,127.2025,141.85714285714286,157.33333333333334,129.4,117.75,155.4,152.8,
23,139.6,153.6,168.09,141.14285714285714,135.0,131.8,122.6,156.8,,
24,138.8,155.6,123.42857142857143,143.57142857142858,140.33333333333334,,136.218,155.8,148.2,140.33333333333334
25,136.0,,165.61,144.14285714285714,,,117.458,,,
26,,,164.11,145.28571428571428,,,119.94200000000001,,,
27,,160.8,162.11,146.85714285714286,157.33333333333334,156.33333333333334,124.796,156.2,159.2,
28,136.6,163.4,118.28571428571429,148.57142857142858,158.66666666666666,154.0,134.12199999999999,157.0,156.6,
29,136.0,165.6,118.57142857142857,148.71428571428572,158.33333333333334,154.66666666666666,123.418,,156.0,140.0
30,135.6,167.8,130.28571428571428,151.0,156.66666666666666,153.33333333333334,116.53600000000002,162.0,,147.0
31,133.8,169.0,130.28571428571428,149.85714285714286,157.66666666666666,157.0,117.93800000000002,,155.8,153.16666666666666
32,135.0,,158.16,135.14285714285714,,,119.39000000000001,,,
33,,,157.88,137.85714285714286,,,121.502,,,148.83333333333334
34,,169.6,129.42857142857142,142.42857142857142,157.0,,124.15599999999999,154.8,,
35,,169.8,130.57142857142858,151.85714285714286,158.0,153.0,127.25399999999999,151.6,,
36,135.3,169.8,131.14285714285714,148.28571428571428,157.0,145.5,141.254,151.6,149.2,
37,135.1,168.0,154.04,150.0,160.33333333333334,151.66666666666666,127.704,151.6,,
38,135.2,169.2,126.85714285714286,157.14285714285714,158.33333333333334,,117.78000000000002,,,151.83333333333334
39,135.2,,153.07,151.14285714285714,,,117.09400000000001,,,
40,,,152.23,150.0,,,116.36800000000001,,,
41,,,126.85714285714286,150.71428571428572,157.33333333333334,148.66666666666666,116.23799999999999,149.0,141.2,
42,135.0,168.4,149.89,158.42857142857142,159.0,144.0,116.96,145.2,,
43,134.9,169.0,125.42857142857143,158.28571428571428,153.66666666666666,152.0,110.06199999999998,,135.8,142.66666666666666
44,136.2,170.2,150.0,157.85714285714286,150.33333333333334,150.66666666666666,110.828,147.2,167.4,
45,136.8,169.6,125.14285714285714,159.0,153.0,,113.33599999999998,151.0,166.6,148.16666666666666
46,128.4,,,158.42857142857142,,,117.98400000000001,,,153.33333333333334
47,,,,159.28571428571428,,166.0,119.974,,,
48,,,,160.85714285714286,148.66666666666666,164.0,121.99600000000001,,167.6,
49,124.4,,,159.28571428571428,154.33333333333334,160.66666666666666,130.964,,167.6,
50,124.9,,,159.71428571428572,118.33333333333333,,148.8,,171.8,
51,123.7,,,,128.66666666666666,,140.8,,167.6,143.16666666666666
52,122.2,,,,131.0,,149.0,,168.0,
53,117.8,,,,,,145.6,,,
54,,,,,,,148.0,,,
55,,,,,150.66666666666666,160.33333333333334,149.2,,168.0,
56,114.3,,,,154.0,151.66666666666666,144.0,,,
57,110.7,,,,156.0,157.0,155.8,,168.2,151.33333333333334
58,114.4,,,,158.33333333333334,157.33333333333334,156.8,,169.0,148.66666666666666
59,112.4,,,,160.33333333333334,,160.2,,168.2,146.83333333333334
60,109.4,,,,,,159.8,,,146.5
61,,,,,,157.33333333333334,159.8,,,144.83333333333334
62,,,,,161.0,158.0,161.0,,165.6,
63,105.1,,,,160.33333333333334,163.0,152.8,,,
64,106.9,,,,156.33333333333334,,148.4,,167.8,146.66666666666666
65,102.3,,,,157.0,,148.0,,168.4,
66,101.7,,,,156.33333333333334,,152.8,,166.4,154.0
67,96.3,,,,,,145.6,,,153.33333333333334
68,,,,,,154.66666666666666,,,,
69,,,,,156.66666666666666,154.0,,,166.6,
70,,,,,130.33333333333334,153.0,,,,
71,,,,,133.0,153.0,,,,148.33333333333334
72,,,,,137.0,,,,,
73,,,,,142.0,,,,,151.16666666666666
74,,,,,,,,,,
75,,,,,,150.0,,,,144.66666666666666
76,,,,,,149.33333333333334,,,,
77,,,,,,150.5,,,,
78,,,,,140.83333333333334,149.52666666666667,,,,
79,,,,,141.33333333333334,151.21,,,,
80,,,,,141.33333333333334,149.6,,,,150.66666666666666
81,,,,,,150.24,,,,
82,,,,,,148.51333333333332,,,,
83,,,,,140.33333333333334,147.95000000000002,,,,
84,,,,,140.0,149.41333333333333,,,,
85,,,,,139.66666666666666,147.42333333333332,,,,143.33333333333334
86,,,,,140.33333333333334,147.53666666666666,,,,
87,,,,,139.66666666666666,,,,,140.66666666666666
88,,,,,,,,,,
89,,,,,,,,,,141.66666666666666
90,,,,,140.0,,,,,
91,,,,,,,,,,
92,,,,,,,,,,
93,,,,,,,,,,143.66666666666666
94,,,,,,,,,,150.33333333333334
95,,,,,,,,,,150.0
96,,,,,,,,,,
97,,,,,,,,,,
19 changes: 11 additions & 8 deletions scripts/pipeline-template.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,9 @@ def map_technology(trial_id: str) -> str:

OPERATING_CONDITIONS_PATH = DATA_DIR / "Donated Data 2023 - Compiled Facility Conditions for DSI.xlsx"

# TODO: Set this up so we can actually plot the full temperature data
df_temps = pd.read_excel(OPERATING_CONDITIONS_PATH, sheet_name=3, skiprows=1, index_col="Day #")
df_temps.columns = [trial2id[col.replace("*", "")] for col in df_temps.columns]
df_avg_temps = df_temps.mean().to_frame("Average Temperature (F)")
df_temps_avg = df_temps.mean().to_frame("Average Temperature (F)")

df_trial_duration = pd.read_excel(
OPERATING_CONDITIONS_PATH,
Expand All @@ -125,9 +124,9 @@ def map_technology(trial_id: str) -> str:

df_moisture = pd.read_excel(OPERATING_CONDITIONS_PATH, sheet_name=4, skiprows=1, index_col="Week")
df_moisture.columns = [trial2id[col.replace("*", "")] for col in df_moisture.columns]
df_moisture = df_moisture.mean().to_frame("Average % Moisture (In Field)")
df_moisture_avg = df_moisture.mean().to_frame("Average % Moisture (In Field)")

df_operating_conditions = pd.concat([df_trial_duration, df_avg_temps, df_moisture], axis=1)
df_operating_conditions_avg = pd.concat([df_trial_duration, df_temps_avg, df_moisture_avg], axis=1)

processed_data = []

Expand Down Expand Up @@ -585,11 +584,15 @@ def preprocess_data(self, data: pd.DataFrame) -> pd.DataFrame:

# Make sure all trial IDs are represented in operating conditions
unique_trial_ids = pd.DataFrame(all_trials["Trial ID"].unique(), columns=["Trial ID"]).set_index("Trial ID")
df_operating_conditions = unique_trial_ids.merge(
df_operating_conditions, left_index=True, right_index=True, how="left"
df_operating_conditions_avg = unique_trial_ids.merge(
df_operating_conditions_avg, left_index=True, right_index=True, how="left"
)

operating_conditions_output_path = DATA_DIR / "operating_conditions.csv"
df_operating_conditions.to_csv(operating_conditions_output_path, index_label="Trial ID")
operating_conditions_output_path = DATA_DIR / "operating_conditions_avg.csv"
df_operating_conditions_avg.to_csv(operating_conditions_output_path, index_label="Trial ID")

# Save full temperature data (TODO: Currently testing this...)
temperature_output_path = DATA_DIR / "temperature_data.csv"
df_temps.to_csv(temperature_output_path, index=True)

print("Complete!")

0 comments on commit 55ee1b5

Please sign in to comment.