From 5f677128141f3f7042af353dcaaeb67a1fb6df27 Mon Sep 17 00:00:00 2001 From: Mohamed Abuelanin Date: Sat, 2 Nov 2024 14:40:23 -0700 Subject: [PATCH 1/8] initial filters implemented --- index.html | 342 ++++++++++++++++++++++++++++++++++++----------- snipe-explore.js | 2 +- styles.css | 97 +++++++++++++- 3 files changed, 360 insertions(+), 81 deletions(-) diff --git a/index.html b/index.html index 22eae71..eb7d1b8 100644 --- a/index.html +++ b/index.html @@ -1020,6 +1020,20 @@
Plot ${plotCounte document.getElementById(`close-plot-${plotId}`).addEventListener('click', () => closePlot(plotId)); document.getElementById(`clone-plot-${plotId}`).addEventListener('click', () => clonePlot(plotId)); + const plotHeader = document.querySelector(`#container-${plotId} .card-header`); + const filterButton = document.createElement('button'); + filterButton.id = `filter-plot-${plotId}`; + filterButton.className = 'btn btn-light btn-sm mr-1'; + filterButton.title = 'Filter'; + filterButton.innerHTML = ''; + plotHeader.appendChild(filterButton); + + // Event listener for Filter button + filterButton.addEventListener('click', () => { + openFilterModal(plotId); + }); + + // Initialize plot if (!config.plotType) { updatePlot(plotId); @@ -1645,12 +1659,42 @@
Plot ${plotCounte const assayTypeCheckboxes = document.querySelectorAll(`#assay-type-checkboxes-${plotId} input[name="assay-type-${plotId}"]:checked`); const selectedAssayTypes = Array.from(assayTypeCheckboxes).map(checkbox => checkbox.value); - currentPlotData = selectedAssayTypes.length > 0 ? data.filter(row => { - const assayType = row["Assay type"] || 'Unknown'; - return selectedAssayTypes.includes(assayType); - }) : data; + let plotDataSet = selectedAssayTypes.length > 0 ? data.filter(row => selectedAssayTypes.includes(row["Assay type"])) : data; + + // Apply filters if any + if (plotFilters[plotId] && plotFilters[plotId].length > 0) { + plotFilters[plotId].forEach(filter => { + const { logic, column, operator, value } = filter; + switch (operator) { + case 'equals': + plotDataSet = logic === 'AND' ? plotDataSet.filter(row => row[column] == value) + : plotDataSet.filter(row => row[column] != value); + break; + case 'not_equals': + plotDataSet = logic === 'AND' ? plotDataSet.filter(row => row[column] != value) + : plotDataSet.filter(row => row[column] == value); + break; + case 'greater_than': + plotDataSet = plotDataSet.filter(row => parseFloat(row[column]) > parseFloat(value)); + break; + case 'less_than': + plotDataSet = plotDataSet.filter(row => parseFloat(row[column]) < parseFloat(value)); + break; + case 'in': + const inValues = value.split(',').map(v => v.trim()); + plotDataSet = plotDataSet.filter(row => inValues.includes(row[column])); + break; + case 'not_in': + const notInValues = value.split(',').map(v => v.trim()); + plotDataSet = plotDataSet.filter(row => !notInValues.includes(row[column])); + break; + default: + break; + } + }); + } - currentPlotData = selectedAssayTypes.length > 0 ? data.filter(row => selectedAssayTypes.includes(row["Assay type"])) : data; + currentPlotData = plotDataSet; if (plotType === 'scatter') { const xAxis = document.getElementById(`x-axis-${plotId}`).value; @@ -2338,6 +2382,7 @@
Plot ${plotCounte } // Updated importSession function + // Update importSession function to restore filters function importSession(jsonData, userData = null) { try { // Show loading modal @@ -2350,6 +2395,7 @@
Plot ${plotCounte plotsContainer.innerHTML = ''; plotCounter = 0; selectedPoints = []; + plotFilters = {}; // Restore species, reference, and amplicon selections document.getElementById('species-select').value = sessionData.selectedSpecies; @@ -2380,7 +2426,7 @@
Plot ${plotCounte // Restore plot type document.getElementById(`plot-type-${plotConfig.plotId}`).value = plotConfig.plotType; - renderDynamicControls(plotConfig.plotId); + renderDynamicControls(plotConfig.plotId, plotConfig); // Restore controls if (plotConfig.plotType === 'scatter') { @@ -2420,6 +2466,11 @@
Plot ${plotCounte document.getElementById(`group-by-${plotConfig.plotId}`).value = plotConfig.controls.groupBy; } + // Collect filters + if (plotConfig.filters && plotConfig.filters.length > 0) { + plotFilters[plotConfig.plotId] = plotConfig.filters; + } + // Restore selected assay types const assayTypeCheckboxes = document.querySelectorAll( `#assay-type-checkboxes-${plotConfig.plotId} input[name="assay-type-${plotConfig.plotId}"]` @@ -2465,9 +2516,10 @@
Plot ${plotCounte // Hide loading modal document.getElementById('loading-modal').style.display = 'none'; }, false); - } catch (error) { - alert('Failed to import session: ' + error.message); - // Hide loading modal in case of error + } + catch (error) { + console.error('Error importing session:', error); + alert('Error importing session. Please check the console for more details.'); document.getElementById('loading-modal').style.display = 'none'; } } @@ -2517,6 +2569,7 @@
Plot ${plotCounte controls: {}, title: document.getElementById(`title-${plotId}`).value, notes: document.getElementById(`notes-${plotId}`).value, + filters: plotFilters[plotId] ? plotFilters[plotId] : [] }; // Collect control values based on plot type @@ -2600,89 +2653,203 @@
Plot ${plotCounte } - // Helper function to collect session data - function collectSessionData() { - const sessionData = { - metadata: { - exportDate: new Date().toISOString(), - dataFilePath: dataFilePath, - }, - selectedSpecies: document.getElementById('species-select').value, - selectedReference: document.getElementById('reference-select').value, - selectedAmplicon: document.getElementById('amplicon-select').value, - plots: [], - selectedPoints: selectedPoints.map(p => p._uniqueId), + + // Add the following JavaScript code at the end of your existing diff --git a/styles.css b/styles.css index 08bc187..dab2beb 100644 --- a/styles.css +++ b/styles.css @@ -1173,3 +1173,139 @@ tr:nth-child(even) { #apply-filters:hover, #reset-filters:hover { background-color: #27ae60; } + + +/* Styles for Loading Modal */ +#loading-modal { + display: none; /* Hidden by default */ + position: fixed; + z-index: 1000; /* Ensure it's on top */ + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.5); /* Semi-transparent background */ +} + +#loading-modal .modal-content { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #fff; + padding: 20px; + border-radius: 8px; + text-align: center; +} + +.spinner { + border: 8px solid #f3f3f3; /* Light grey */ + border-top: 8px solid #3498db; /* Blue */ + border-radius: 50%; + width: 60px; + height: 60px; + animation: spin 2s linear infinite; + margin: 0 auto 10px auto; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Styles for Filter Modal */ +#filter-modal .modal-content { + max-height: 80vh; + overflow-y: auto; + width: 500px; /* Adjust width as needed */ + padding: 20px; + position: relative; + background-color: #fff; + border-radius: 8px; +} + +/* Filter Group Layout */ +.filter-group { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + position: relative; +} + +/* Logic Selector Styling */ +.logic-div { + text-align: center; + width: 100%; +} + +.logic-select { + width: 150px; + margin-bottom: 10px; +} + +/* Condition Div Styling */ +.condition-div { + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; +} + +/* Remove Button Styling */ +.filter-group .remove-filter-group { + background: none; + border: none; + color: #e74c3c; + cursor: pointer; + font-size: 16px; + position: absolute; + top: 10px; + right: 10px; +} + +/* Action Buttons Styling */ +.button { + margin-top: 10px; + margin-right: 10px; +} + +/* Range Slider Styling */ +.range-slider { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + margin-top: 10px; +} + +.slider-inputs { + display: flex; + align-items: center; + gap: 10px; + width: 100%; +} + +.slider-inputs input[type="number"] { + width: 80px; + padding: 5px; + font-size: 0.9rem; + border: 1px solid #ccc; + border-radius: 4px; +} + +.tags-container .tag { + font-size: 0.9em; +} + +/* Adjust slider width */ +.range-slider { + margin-bottom: 10px; +} + +/* Optional: Adjust the width of the range inputs */ +.range-min, .range-max { + width: 80px; +} \ No newline at end of file From 9439690ae594492de68aaba0fad8408f552e9462 Mon Sep 17 00:00:00 2001 From: Mohamed Abuelanin Date: Sun, 3 Nov 2024 08:14:05 -0800 Subject: [PATCH 3/8] dev checkpoint fixed import --- index.html | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/index.html b/index.html index c612317..8a90e36 100644 --- a/index.html +++ b/index.html @@ -2447,9 +2447,9 @@
Plot ${plotCounte initializeHueSlider(plotConfig.plotId); // Restore slider positions - const xSlider = document.getElementById(`slider-${plotConfig.plotId}-filter-0-range`); // Adjust filter index as needed - const ySlider = document.getElementById(`slider-${plotConfig.plotId}-filter-1-range`); // Adjust filter index as needed - const hueSlider = document.getElementById(`slider-${plotConfig.plotId}-filter-2-range`); // Adjust filter index as needed + const xSlider = document.getElementById(`x-range-slider-${plotConfig.plotId}`); + const ySlider = document.getElementById(`y-range-slider-${plotConfig.plotId}`); + const hueSlider = document.getElementById(`hue-range-slider-${plotConfig.plotId}`); if (xSlider && xSlider.noUiSlider && plotConfig.controls.xRange) { xSlider.noUiSlider.set([plotConfig.controls.xRange.min, plotConfig.controls.xRange.max]); @@ -2462,7 +2462,7 @@
Plot ${plotCounte } } else if (plotConfig.plotType === 'histogram' || plotConfig.plotType === 'boxplot') { plotConfig.controls.numColumns.forEach(col => { - const checkbox = document.getElementById(`num-column-${col}-${plotConfig.plotId}`); + const checkbox = document.querySelector(`#checkbox-group-${plotConfig.plotId} input[value="${col}"]`); if (checkbox) { checkbox.checked = true; } @@ -2470,13 +2470,6 @@
Plot ${plotCounte document.getElementById(`group-by-${plotConfig.plotId}`).value = plotConfig.controls.groupBy; } - // Collect filters - if (plotConfig.filters && plotConfig.filters.length > 0) { - plotConfig.filters.forEach(filterGroup => { - addFilterGroup(filterGroup); - }); - } - // Restore selected assay types const assayTypeCheckboxes = document.querySelectorAll( `#assay-type-checkboxes-${plotConfig.plotId} input[name="assay-type-${plotConfig.plotId}"]` @@ -2485,10 +2478,8 @@
Plot ${plotCounte checkbox.checked = plotConfig.controls.selectedAssayTypes.includes(checkbox.value); }); - // Restore title and notes - document.getElementById(`title-${plotConfig.plotId}`).value = plotConfig.title; - document.getElementById(`plot-title-display-${plotConfig.plotId}`).textContent = plotConfig.title || `Plot ${plotConfig.plotId.split('-')[1]}`; - document.getElementById(`notes-${plotConfig.plotId}`).value = plotConfig.notes; + // Set plot filters from session data + plotFilters[plotConfig.plotId] = plotConfig.filters || []; // Update the plot updatePlot(plotConfig.plotId); From ebfd5a5a080ba31368b647aee4fe5e36562cc8c9 Mon Sep 17 00:00:00 2001 From: Mohamed Abuelanin Date: Sun, 3 Nov 2024 11:37:16 -0800 Subject: [PATCH 4/8] more stable filters, but missing categories --- index.html | 322 +++++++++++++++++++++++++++++------------------------ 1 file changed, 179 insertions(+), 143 deletions(-) diff --git a/index.html b/index.html index 8a90e36..03d8f7c 100644 --- a/index.html +++ b/index.html @@ -895,87 +895,91 @@

Selected Data Points

} - // Updated addNewPlot function function addNewPlot(config = {}) { plotCounter++; const plotId = `plot-${plotCounter}`; let plotHTML = ` -
- -
- -
- -
- - - -