Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GLT-4245] TAT trend report legend for Color by Gate #228

Merged
merged 3 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/add_GLT-4245
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TAT Trend report legend for colour by gate
1 change: 1 addition & 0 deletions changes/fix_GLT-4245
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TAT Trend Report color by gate feature to work correctly with time range grouping
12 changes: 12 additions & 0 deletions src/main/resources/static/css/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,10 @@ Ensure the default browser behavior of the `hidden` attribute.
display: none;
}

.h-3{
height: 0.75rem;
}

.h-full{
height: 100%;
}
Expand All @@ -666,6 +670,10 @@ Ensure the default browser behavior of the `hidden` attribute.
min-height: 100vh;
}

.w-3{
width: 0.75rem;
}

.w-6{
width: 1.5rem;
}
Expand Down Expand Up @@ -800,6 +808,10 @@ Ensure the default browser behavior of the `hidden` attribute.
justify-content: space-between;
}

.gap-1{
gap: 0.25rem;
}

.gap-2{
gap: 0.5rem;
}
Expand Down
68 changes: 34 additions & 34 deletions src/main/resources/templates/tat-trend.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,49 +11,49 @@
<div layout:fragment="content">
<h1 class="text-green-200 font-sarabun font-light text-32">TAT Trend Report</h1>
<div id="trendReportContainer">
<div id="controlsContainer">
<div id="groupingButtons" style="display: flex; gap: 10px; margin-top: 10px;">
<div id="controlsContainer" class="flex items-center justify-between gap-2 mt-2">
<div id="groupingButtons" class="flex gap-2">
<button id="weekButton" data-grouping="week"
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1 flex space-x-2 items-center">Week</button>
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1">Week</button>
<button id="monthButton" data-grouping="month"
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1 flex space-x-2 items-center">Month</button>
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1">Month</button>
<button id="quarterButton" data-grouping="fiscalQuarter"
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1 flex space-x-2 items-center">Fiscal
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1">Fiscal
Quarter</button>
<button id="yearButton" data-grouping="fiscalYear"
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1 flex space-x-2 items-center">Fiscal
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1">Fiscal
Year</button>
<div style="margin-left: auto; display: flex; align-items: center; gap: 10px;">
<label class="font-inter font-medium text-12">
<input type="checkbox" id="toggleColors"> Color by Gate
</label>
<select id="dataSelection"
class="font-inter font-medium text-12 text-black bg-grey-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1 flex space-x-2 items-center">
<option value="ClinicalReport">Clinical Report (CR)</option>
<option value="DataRelease">Data Release (DR)</option>
<option value="All">All</option>
</select>
</div>
</div>
<div id="gatesCheckboxes" style="display: flex; gap: 10px; margin-top: 10px;">
<label class="font-inter font-medium text-12"><input type="checkbox" value="Receipt" checked> Receipt</label>
<label class="font-inter font-medium text-12"><input type="checkbox" value="Extraction" checked>
Extraction</label>
<label class="font-inter font-medium text-12"><input type="checkbox" value="Library Prep" checked> Library
Prep.</label>
<label class="font-inter font-medium text-12"><input type="checkbox" value="Library Qual" checked> Library
Qual.</label>
<label class="font-inter font-medium text-12"><input type="checkbox" value="Full-Depth" checked>
Full-Depth</label>
<label class="font-inter font-medium text-12"><input type="checkbox" value="Analysis Review" checked> Analysis
Review</label>
<label class="font-inter font-medium text-12"><input type="checkbox" value="Release Approval" checked> Release
Approval</label>
<label class="font-inter font-medium text-12"><input type="checkbox" value="Release" checked> Release</label>
<label class="font-inter font-medium text-12"><input type="checkbox" value="Full Case" checked> All
Completed</label>
<div class="flex items-center gap-2">
<button id="legendButton"
class="noprint bg-green-200 rounded-md hover:ring-2 ring-offset-1 ring-green-200 text-white font-inter font-medium text-12 px-2 py-1 hidden">Legend</button>
<label class="font-inter font-medium text-12 flex items-center gap-1">
<input type="checkbox" id="toggleColors"> Colour by Gate
</label>
<select id="dataSelection"
class="font-inter font-medium text-12 text-black bg-gray-100 px-2 py-1 rounded-md hover:ring-2 ring-green-200 ring-offset-1">
<option value="ClinicalReport">Clinical Report (CR)</option>
<option value="DataRelease">Data Release (DR)</option>
<option value="All">All</option>
</select>
</div>
</div>
<div id="gatesCheckboxes" class="flex gap-2 mt-2 font-inter font-medium text-12 items-center">
<label class="flex items-center gap-1"><input type="checkbox" value="Receipt" checked> Receipt</label>
<label class="flex items-center gap-1"><input type="checkbox" value="Extraction" checked> Extraction</label>
<label class="flex items-center gap-1"><input type="checkbox" value="Library Prep" checked> Library
Prep.</label>
<label class="flex items-center gap-1"><input type="checkbox" value="Library Qual" checked> Library
Qual.</label>
<label class="flex items-center gap-1"><input type="checkbox" value="Full-Depth" checked> Full-Depth</label>
<label class="flex items-center gap-1"><input type="checkbox" value="Analysis Review" checked> Analysis
Review</label>
<label class="flex items-center gap-1"><input type="checkbox" value="Release Approval" checked> Release
Approval</label>
<label class="flex items-center gap-1"><input type="checkbox" value="Release" checked> Release</label>
<label class="flex items-center gap-1"><input type="checkbox" value="Full Case" checked> Full Case</label>
</div>

<div id="plotContainer"></div>
</div>
<script src="/js/tatTrend.js"></script>
Expand Down
41 changes: 35 additions & 6 deletions ts/component/legend.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { qcStatuses } from "../data/qc-status";
import { makeIcon } from "../util/html-utils";
import { GATE_COLOR_MAPPING, getColorForGate } from "../util/color-mapping";

const legendId = "legend-container";

class Legend {
private container: HTMLElement;
constructor() {
constructor(type: "qc" | "gate" = "qc") {
// outer container
this.container = document.createElement("div");
this.container.className =
Expand Down Expand Up @@ -38,10 +40,20 @@ class Legend {
// grid of legend labels
const body = document.createElement("div");
body.className = "m-4 grid grid-rows-5 grid-flow-col gap-2";
for (const qcStatus of Object.values(qcStatuses)) {
body.appendChild(makeLegendEntry(qcStatus.icon, qcStatus.label));
// populate legend based on type
if (type === "qc") {
for (const qcStatus of Object.values(qcStatuses)) {
body.appendChild(makeLegendEntry(qcStatus.icon, qcStatus.label));
}
body.appendChild(makeLegendEntry("pen-ruler", "Preliminary value"));
} else if (type === "gate") {
for (const gate of Object.keys(GATE_COLOR_MAPPING)) {
const color = getColorForGate(gate);
body.appendChild(makeLegendEntryColor(color, gate));
}
} else {
throw new Error(`Unsupported legend type: ${type}`);
}
body.appendChild(makeLegendEntry("pen-ruler", "Preliminary value"));

this.container.append(header);
this.container.append(body);
Expand Down Expand Up @@ -152,10 +164,10 @@ class Legend {
}
}

export function toggleLegend() {
export function toggleLegend(type: "qc" | "gate" = "qc") {
const legendWindow = document.getElementById(legendId);
if (!legendWindow) {
const legendContainer = new Legend();
const legendContainer = new Legend(type);
document.body.appendChild(legendContainer.getTag());
} else {
legendWindow.remove();
Expand All @@ -174,3 +186,20 @@ function makeLegendEntry(iconName: string, text: string) {
labelContainer.appendChild(label);
return labelContainer;
}

function makeLegendEntryColor(color: string, text: string) {
const labelContainer = document.createElement("div");
labelContainer.className =
"flex items-center space-x-2 bg-grey-100 rounded-md font-inter font-medium p-2 text-12";
// create the color box
const colorBox = document.createElement("div");
colorBox.className = "w-3 h-3 inline-block border-2";
colorBox.style.backgroundColor = `${color}B3`;
colorBox.style.borderColor = color;
const label = document.createElement("span");
label.innerHTML = text;
// append the color box and label to the container
labelContainer.appendChild(colorBox);
labelContainer.appendChild(label);
return labelContainer;
}
2 changes: 1 addition & 1 deletion ts/component/table-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1056,5 +1056,5 @@ function getElement<Type>(element?: Type) {

export const legendAction: StaticAction = {
title: "Legend",
handler: toggleLegend,
handler: () => toggleLegend("qc"),
};
74 changes: 42 additions & 32 deletions ts/tat-trend.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Plotly from "plotly.js-dist-min";
import { post } from "./util/requests";
import { getRequiredElementById } from "./util/html-utils";
import { toggleLegend } from "./component/legend";
import { getColorForGate } from "./util/color-mapping";

let jsonData: any[] = [];
const uirevision = "true";
Expand Down Expand Up @@ -259,7 +261,7 @@ function plotData(
pointpos: 0,
marker: {
size: 6,
color: colorByGate ? gateColors[gate] : assayColors[assay],
color: colorByGate ? getColorForGate(gate) : assayColors[assay],
},
boxmean: true,
legendgroup: assay,
Expand Down Expand Up @@ -344,6 +346,31 @@ function updatePlot(
Plotly.react("plotContainer", newPlot, layout);
}

function updatePlotWithLegend(
selectedGrouping: string,
jsonData: any[],
selectedGates: string[],
selectedDataType: string
) {
updatePlot(selectedGrouping, jsonData, selectedGates, selectedDataType);
const legendButton = document.getElementById("legendButton");
if (getColorByGate()) {
// show the Legend button
if (legendButton) {
legendButton.classList.remove("hidden");
}
} else {
// hide the Legend button and close the legend if it's open
if (legendButton) {
legendButton.classList.add("hidden");
}
const legendElement = document.getElementById("legend-container");
if (legendElement) {
legendElement.remove(); // hide the legend if 'color by gate' is deselected
}
}
}

function parseUrlParams(): { key: string; value: string }[] {
const params: { key: string; value: string }[] = [];
const searchParams = new URLSearchParams(window.location.search);
Expand All @@ -364,9 +391,7 @@ function getSelectedGrouping(): string {
const selectedButton = document.querySelector<HTMLButtonElement>(
"#groupingButtons button.active"
);
return selectedButton
? selectedButton.id.replace("Button", "")
: "fiscalQuarter";
return selectedButton ? selectedButton.dataset.grouping! : "fiscalQuarter";
}

function getSelectedDataType(): string {
Expand All @@ -376,7 +401,7 @@ function getSelectedDataType(): string {
return dataSelection.value;
}

document.addEventListener("DOMContentLoaded", () => {
window.addEventListener("load", () => {
const params = parseUrlParams();
const requestData = { filters: params.length > 0 ? params : [] };

Expand All @@ -395,10 +420,11 @@ document.addEventListener("DOMContentLoaded", () => {
});

const handlePlotUpdate = () => {
const selectedGrouping = getSelectedGrouping();
const selectedGates = getSelectedGates();
const selectedDataType = getSelectedDataType();
updatePlot(
getSelectedGrouping(),
updatePlotWithLegend(
selectedGrouping,
jsonData,
selectedGates,
selectedDataType
Expand All @@ -410,33 +436,17 @@ document.addEventListener("DOMContentLoaded", () => {
buttons.forEach((button) => button.classList.remove("active"));
(event.currentTarget as HTMLButtonElement).classList.add("active");

const selectedGrouping = (event.currentTarget as HTMLButtonElement).dataset
.grouping;
const selectedGates = getSelectedGates();
const selectedDataType = getSelectedDataType();
newPlot(selectedGrouping!, jsonData, selectedGates, selectedDataType);
handlePlotUpdate();
};

const weekButton = getRequiredElementById("weekButton");
const monthButton = getRequiredElementById("monthButton");
const quarterButton = getRequiredElementById("quarterButton");
const yearButton = getRequiredElementById("yearButton");
["weekButton", "monthButton", "quarterButton", "yearButton"].forEach((id) => {
getRequiredElementById(id).addEventListener("click", handleNewPlot);
});

weekButton.addEventListener("click", handleNewPlot);
monthButton.addEventListener("click", handleNewPlot);
quarterButton.addEventListener("click", handleNewPlot);
yearButton.addEventListener("click", handleNewPlot);
["dataSelection", "gatesCheckboxes", "toggleColors"].forEach((id) => {
getRequiredElementById(id).addEventListener("change", handlePlotUpdate);
});

getRequiredElementById("dataSelection").addEventListener(
"change",
handlePlotUpdate
);
getRequiredElementById("gatesCheckboxes").addEventListener(
"change",
handlePlotUpdate
);
getRequiredElementById("toggleColors").addEventListener(
"change",
handlePlotUpdate
);
const legendButton = getRequiredElementById("legendButton");
legendButton.addEventListener("click", () => toggleLegend("gate"));
});
15 changes: 15 additions & 0 deletions ts/util/color-mapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const GATE_COLOR_MAPPING: { [gate: string]: string } = {
"Receipt": "#4477AA",
"Extraction": "#66CCEE",
"Library Prep": "#228833",
"Library Qual": "#CCBB44",
"Full-Depth": "#EE6677",
"Analysis Review": "#44AA99",
"Release Approval": "#AA3377",
"Release": "#BBBBBB",
"Full Case": "#000000",
};

export function getColorForGate(gate: string): string {
return GATE_COLOR_MAPPING[gate] || "#DDDDDD";
}
Loading