diff --git a/dashboard-react/components/CheckboxMenu.js b/dashboard-react/components/CheckboxMenu.js index 094dd5e..bfaece2 100644 --- a/dashboard-react/components/CheckboxMenu.js +++ b/dashboard-react/components/CheckboxMenu.js @@ -39,22 +39,6 @@ export default function CheckboxMenu({ const allSelected = selectedOptions.length === options.length; const noneSelected = selectedOptions.length === 0; - const [canScrollUp, setCanScrollUp] = useState(false); - const [canScrollDown, setCanScrollDown] = useState(false); - const scrollRef = useRef(null); - - const checkScrollPosition = () => { - const element = scrollRef.current; - setCanScrollUp(element.scrollTop > 0); - setCanScrollDown( - element.scrollTop < element.scrollHeight - element.clientHeight - ); - }; - - useEffect(() => { - checkScrollPosition(); // Initial check - }, [options]); // Re-check when options change - return (
@@ -89,19 +73,15 @@ export default function CheckboxMenu({
-
- {canScrollUp ? ( -
- ▲ -
- ) : ( -
- )} +
+ className="absolute top-0 left-0 right-0 h-6 pointer-events-none z-10" + style={{ + background: + "linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0))", + }} + >
{" "} +
    {options?.map((option) => (
  • @@ -132,13 +112,13 @@ export default function CheckboxMenu({ ))}
- {canScrollDown ? ( -
- ▼ -
- ) : ( -
- )} +
{" "}
); diff --git a/dashboard-react/components/Dashboard.js b/dashboard-react/components/Dashboard.js index 7748e14..0b403cb 100644 --- a/dashboard-react/components/Dashboard.js +++ b/dashboard-react/components/Dashboard.js @@ -129,6 +129,9 @@ export default function Dashboard() { tickangle: xTickAngle, ticklen: 10, automargin: true, + tickfont: { + weight: "bold", + }, }, hovermode: "x", }} diff --git a/dashboard-react/components/DashboardFilterControls.js b/dashboard-react/components/DashboardFilterControls.js index 802211b..a035c60 100644 --- a/dashboard-react/components/DashboardFilterControls.js +++ b/dashboard-react/components/DashboardFilterControls.js @@ -58,14 +58,14 @@ export default function DashboardFilterControls() { options={Object.keys(temperatureFilterDict)} selectedOptions={snap.filters.selectedTemperatureLevels} filterKey="selectedTemperatureLevels" - title="Average Temperature" + title="Mean Temperature" infoText="Select one or more options to filter the data by the average temperature range during the field test." /> { csv("/data/operating_conditions.csv") .then((data) => { const formattedData = []; - const selectedColumn = selectedMetric === "Temperature" ? "Temperature" @@ -29,14 +32,9 @@ export default function OperatingConditionsDashboard({ ? "Oxygen" : null; - const filteredData = data.filter( + let filteredData = data.filter( (d) => d["Operating Condition"] === selectedColumn ); - let timeSteps = filteredData.map((d) => d["Time Step"]); - - if (selectedMetric !== "Temperature") { - timeSteps = timeSteps.map((d) => d * 7); // Convert weeks to days - } const nonTrialColumns = [ "Time Step", @@ -44,6 +42,31 @@ export default function OperatingConditionsDashboard({ "Time Unit", ]; + filteredData = filteredData.filter((row) => { + // Check if all trial columns are empty (null, undefined, or empty string) + return Object.keys(row).some( + (col) => + !nonTrialColumns.includes(col) && + row[col] !== null && + row[col] !== undefined && + row[col] !== "" + ); + }); + + console.log("Filtered Data:", filteredData); + + let timeSteps = filteredData.map((d) => d["Time Step"]); + if (selectedMetric !== "Temperature") { + timeSteps = timeSteps.map((d) => d * 7); // Convert weeks to days + } + + const maxDaysFromData = Math.max(...timeSteps); + const calculatedEffectiveMaxDays = ignoreMaxDays + ? maxDaysFromData + 5 + : Math.min(maxDays, maxDaysFromData); + + setEffectiveMaxDays(calculatedEffectiveMaxDays); // Update state + const trialCount = {}; // Reset trial count each time data is processed Object.keys(data[0]).forEach((column) => { @@ -53,18 +76,34 @@ export default function OperatingConditionsDashboard({ if (selectedMetric !== "Temperature") { windowSize = 3; // Reduce window size for non-temperature metrics } - yData = movingAverage(yData, windowSize); + if (applyMovingAverage) { + yData = movingAverage(yData, windowSize); + } const trialName = mapTrialName(column, trialCount); formattedData.push({ x: timeSteps, y: yData, - mode: "lines", + mode: "lines+markers", name: trialName, }); } }); + if (selectedMetric === "Temperature") { + formattedData.push({ + x: [0, 45], + y: [131, 131], + mode: "lines", + name: "PFRP", + line: { + dash: "dot", + color: "red", + width: 2, + }, + }); + } + formattedData.sort((a, b) => a.name.localeCompare(b.name)); setPlotData(formattedData); setDataLoaded(true); @@ -73,7 +112,7 @@ export default function OperatingConditionsDashboard({ console.error("Error loading CSV data:", error); setErrorMessage("Failed to load data."); }); - }, [windowSize, selectedMetric]); + }, [windowSize, selectedMetric, ignoreMaxDays, applyMovingAverage]); const mapTrialName = (trialName, trialCount) => { const mappings = { @@ -154,8 +193,8 @@ export default function OperatingConditionsDashboard({ const yMax = plotData.length > 0 - ? Math.max(...plotData.flatMap((d) => d.y.map((y) => y + 0.05)), 1.05) - : 1.05; + ? Math.max(...plotData.flatMap((d) => d.y.map((y) => y + 0.05))) + : null; const xTickAngle = plotData.length > 6 ? 90 : 0; @@ -167,19 +206,6 @@ export default function OperatingConditionsDashboard({
) : ( <> -
- -
${yAxisTitle}`, }, range: [0, yMax], + showline: true, }, xaxis: { title: { @@ -205,7 +232,8 @@ export default function OperatingConditionsDashboard({ tickangle: xTickAngle, ticklen: 10, automargin: true, - range: [0, maxDays], // Cap x-axis at maxDays + range: [0, effectiveMaxDays], + // range: ignoreMaxDays ? null : [0, effectiveMaxDays], showline: true, }, hovermode: "x", @@ -214,6 +242,43 @@ export default function OperatingConditionsDashboard({ displayModeBar: false, }} /> +
+
+ +
+
+ +
+
+ +
+
)} diff --git a/scripts/pipeline-template.py b/scripts/pipeline-template.py index cd564da..a082e7d 100644 --- a/scripts/pipeline-template.py +++ b/scripts/pipeline-template.py @@ -596,8 +596,32 @@ def preprocess_data(self, data: pd.DataFrame) -> pd.DataFrame: # Map Trial IDs to the technology used in the trial all_trials["Technology"] = all_trials["Trial ID"].apply(map_technology) +# Anonymize brand names +brand_mapping = {"BÉSICS®": "BÉSICS®"} # Note: no anonymization for BÉSICS® +brand_counter = 0 + + +def anonymize_brand(brand: str) -> str: + """Anonymizes brand names by mapping them to a generic brand. Sorry for the global variable. + + Args: + brand: The brand name + + Returns: + The anonymized brand name (eg "Brand A") + """ + global brand_counter + if brand not in brand_mapping: + brand_mapping[brand] = f"Brand {chr(65 + brand_counter)}" + brand_counter += 1 + return brand_mapping[brand] + + +all_trials["Item Brand"] = all_trials["Item Brand"].apply(anonymize_brand) + all_trials.to_csv(output_filepath, index=False) + # 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_avg = unique_trial_ids.merge(