Skip to content

Commit

Permalink
Merge pull request #51 from uchicago-dsi/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
toddnief authored Jul 24, 2024
2 parents 01a3e10 + cca09ba commit 4eb4b1d
Show file tree
Hide file tree
Showing 16 changed files with 386 additions and 240 deletions.
27 changes: 20 additions & 7 deletions dashboard-react/app/api/data/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,13 @@ const getIntersectingTrialIDs = (...sets) => {
const prepareData = async (searchParams) => {
console.log("searchParams");
console.log(searchParams);
// TODO: Clean up the handling of defaults
// Display params
const aggCol = searchParams.get("aggcol") || "Material Class I";
const displayCol = searchParams.get("displaycol") || "% Residuals (Mass)";
const uncapResults = searchParams.get("uncapresults") === "true" || false;
const displayResiduals = searchParams.get("displayresiduals") === "true";
// Trial and item filters
const testMethods = searchParams.get("testmethods")
? searchParams.get("testmethods").split(",")
: [];
const testMethod = searchParams.get("testmethod") || "Mesh Bag";
const technologies = searchParams.get("technologies")
? searchParams.get("technologies").split(",")
: [];
Expand All @@ -161,6 +158,20 @@ const prepareData = async (searchParams) => {
? searchParams.get("trialdurations").split(",")
: [];

const noFiltersSelected =
technologies.length === 0 ||
materials.length === 0 ||
temperatureFilter.length === 0 ||
moistureFilter.length === 0 ||
trialDurations.length === 0;

if (noFiltersSelected) {
return {
message:
"None selected for some filtering criteria. Please make sure you have at least one filter selected.",
};
}

let trialData;
let operatingConditions;

Expand All @@ -180,8 +191,7 @@ const prepareData = async (searchParams) => {
var filteredData = [...trialData];

// filter data based on selected filters
// TODO: This is wrong!
filteredData = filterData(filteredData, "Test Method", testMethods);
filteredData = filterData(filteredData, "Test Method", [testMethod]);
console.log("filteredData.length after test methods");
console.log(filteredData.length);
filteredData = filterData(filteredData, "Technology", technologies);
Expand Down Expand Up @@ -257,7 +267,10 @@ const prepareData = async (searchParams) => {

// Not enough data - return empty object
if (filteredData.length < 5) {
return {};
return {
message:
"Not enough data for the selected criteria. Please select more options.",
};
}

const uniqueTrialIDs = new Set(filteredData.map((d) => d["Trial ID"]));
Expand Down
16 changes: 5 additions & 11 deletions dashboard-react/app/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import React, { useEffect } from "react";
import state from "@/lib/state";
import dynamic from "next/dynamic";
import FilterControls from "@/components/FilterControls";
import DashboardControls from "@/components/DashboardControls";

const Dashboard = dynamic(() => import("@/components/Dashboard"), {
ssr: false,
Expand All @@ -23,17 +23,11 @@ const Home = () => {
}, []);

return (
<main
style={{
display: "flex",
flexDirection: "row",
alignItems: "flex-start",
}}
>
<div style={{ marginRight: "20px" }}>
<FilterControls />
<main className="flex flex-row items-start h-screen">
<div className="max-w-[600px]">
<DashboardControls />
</div>
<div style={{ minWidth: "1000px" }}>
<div className="min-w-[1000px] h-full">
<Dashboard />
</div>
</main>
Expand Down
14 changes: 14 additions & 0 deletions dashboard-react/components/Alert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"use client";
import React from "react";

const AlertMessage = ({ message }) => {
return (
<div
className={`border-l-4 p-4 bg-yellow-100 border-yellow-400 text-yellow-700 rounded mt-5`}
>
<p className="font-bold">{message}</p>
</div>
);
};

export default AlertMessage;
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
"use client";
import React, { useRef } from "react";
import React, { useState } from "react";
import { useSnapshot } from "valtio";
import state from "@/lib/state";
import { closeOpenedDetails } from "@/lib/utils";

const DropdownCheckbox = React.memo(function DropdownCheckbox({
export default function CheckboxMenu({
options,
selectedOptions,
filterKey,
title,
}) {
const snap = useSnapshot(state);
const divRef = useRef(null);

const onSummaryClick = () => {
closeOpenedDetails(`summary-${filterKey}`);
};
// Note: Use local state for the expanded state of the menu
const [expanded, setExpanded] = useState(false);

const handleCheckboxChange = (key, value) => (event) => {
const checked = event.target.checked;
console.log(`checked for ${key} ${value}`);
console.log(checked);
if (checked) {
state.setFilterValue(key, [...snap.filters[key], value]);
} else {
console.log("removing");
state.setFilterValue(
key,
snap.filters[key].filter((item) => item !== value)
Expand All @@ -40,26 +32,23 @@ const DropdownCheckbox = React.memo(function DropdownCheckbox({
state.setFilterValue(filterKey, []);
};

const isAllSelected = selectedOptions.length === options.length;

return (
<>
<h2>{title}</h2>
<div className="divider m-0"></div>
<details className="dropdown">
<summary
className="btn m-1"
onClick={onSummaryClick}
ref={divRef}
id={`summary-${filterKey}`}
<div className="my-4">
<h2 className="text-center">{title}</h2>
<div className="flex justify-center mt-3">
<button
className="btn btn-sm normal-case"
onClick={() => setExpanded((e) => !e)}
>
{isAllSelected
? "All Selected"
: selectedOptions.length > 0
? selectedOptions.join(", ")
: "None Selected"}
</summary>
<ul className="menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 shadow">
{expanded ? `Collapse Menu` : `Show Menu`}
</button>
</div>
<div
className={`overflow-auto flex-grow px-4 ${
expanded ? "max-h-[200px]" : "h-0"
}`}
>
<ul className="menu dropdown-content bg-base-100 rounded-box z-[1] w-52 p-2 mx-auto shadow">
{options?.map((option) => (
<li key={option}>
<label className="label cursor-pointer">
Expand All @@ -75,8 +64,8 @@ const DropdownCheckbox = React.memo(function DropdownCheckbox({
</li>
))}
</ul>
</details>
<div className="mt-2 flex join justify-left">
</div>
<div className="mt-2 flex join justify-center">
<button
className="btn join-item btn-sm normal-case"
onClick={selectAll}
Expand All @@ -91,8 +80,6 @@ const DropdownCheckbox = React.memo(function DropdownCheckbox({
</button>
</div>
<div className="divider m-0"></div>
</>
</div>
);
});

export default DropdownCheckbox;
}
48 changes: 39 additions & 9 deletions dashboard-react/components/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import React from "react";
import Plot from "react-plotly.js";
import { useSnapshot } from "valtio";
import state from "@/lib/state";
import { col2material } from "@/lib/constants";
import Alert from "@/components/Alert";

export default function Dashboard() {
const snap = useSnapshot(state);

if (!snap.dataLoaded) {
return <p>Loading data...</p>;
}

const class2color = {
"Positive Control": "#70AD47",
"Mixed Materials": "#48646A",
Expand All @@ -20,9 +26,13 @@ export default function Dashboard() {
console.log(d);
const materialClass = d["Material Class I"];
const color = class2color[materialClass] || "#000";
const countDisplay =
snap.filters["testMethod"] === "Mesh Bag"
? ` (n=${d["count"]})`
: "";
return {
type: "box",
name: `${d["aggCol"]} (n=${d["count"]})`,
name: `${d["aggCol"]}${countDisplay}`,
y: [d.min, d.q1, d.median, d.q3, d.max],
marker: { color },
boxmean: true,
Expand All @@ -43,7 +53,7 @@ export default function Dashboard() {
);

function generateTitle(displayCol, aggCol, num_trials) {
return `${displayCol} by ${aggCol} - ${num_trials} Trial(s)`;
return `${displayCol} by ${col2material[aggCol]} - ${num_trials} Trial(s)`;
}

const title = generateTitle(
Expand All @@ -52,31 +62,51 @@ export default function Dashboard() {
snap.data.numTrials
);

const yMax =
snap.data.data && snap.data.data.length > 0
? Math.max(...snap.data.data.map((d) => d.max), 1)
: 1;

const xTickAngle = plotData.length > 6 ? 90 : 0;

return (
<div style={{ minWidth: "1000px" }}>
{plotData.length > 0 ? (
<div>
{snap.errorMessage ? (
<p>
<Alert message={snap.errorMessage} />
</p>
) : (
<Plot
data={plotData}
layout={{
width: 1000,
height: 700,
height: 800,
title: {
text: title,
text: `<b>${title}</b>`,
x: 0.5,
xanchor: "center",
yanchor: "top",
},
showlegend: false,
yaxis: {
title: {
text: yAxisTitle,
text: `<b>${yAxisTitle}</b>`,
},
tickformat: ".0%",
range: [0, yMax],
},
xaxis: {
tickangle: xTickAngle,
ticklen: 10,
},
margin: {
b: 300,
},
}}
config={{
displayModeBar: false,
}}
/>
) : (
<p>Not enough data for the selected criteria</p>
)}
</div>
);
Expand Down
59 changes: 59 additions & 0 deletions dashboard-react/components/DashboardControls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"use client";
import React, { useEffect, useRef } from "react";
import { useSnapshot } from "valtio";
import state from "@/lib/state";
import DashboardDisplayControls from "@/components/DashboardDisplayControls";
import DashboardFilterControls from "@/components/DashboardFilterControls";

export default function DashboardControls() {
const snap = useSnapshot(state);

useEffect(() => {
if (
snap.options["Test Method"] &&
snap.options["Material Class II"] &&
snap.options["Technology"] &&
state.filters.initialized === false
) {
state.setFilterValue("selectedTestMethods", snap.options["Test Method"]);
state.setFilterValue(
"selectedMaterialTypes",
snap.options["Material Class II"]
);
state.setFilterValue("selectedTechnologies", snap.options["Technology"]);
state.setFilterValue("initialized", true);
}
}, [snap.options]);

useEffect(() => {
const summaries = document.querySelectorAll("summary");

summaries.forEach((summary) => {
summary.addEventListener("click", closeOpenedDetails);
});

function closeOpenedDetails() {
summaries.forEach((summary) => {
let detail = summary.parentNode;
if (detail != this.parentNode) {
detail.removeAttribute("open");
}
});
}
}, [snap.options]);

if (!snap.filters.initialized) {
return <div>Loading...</div>;
}

return (
<div className="flex flex-col h-[100vh] overflow-y-auto px-2">
<div className="m-2">
<DashboardDisplayControls />
</div>
<div className="m-2">
<DashboardFilterControls />
</div>
</div>
);
}
Loading

0 comments on commit 4eb4b1d

Please sign in to comment.