From 38d68fb67588be23e080039d1231072a798eec91 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 9 Aug 2024 17:14:10 -0500 Subject: [PATCH 1/9] allow to filter by collection name of the object --- js/filters/collections/cluster.js | 31 ++++++++++++++-- js/filters/collections/mcparticle.js | 23 ++++++++---- js/filters/collections/particleid.js | 25 ++++++------- js/filters/collections/recoparticle.js | 32 ++++++++++++++-- js/filters/collections/track.js | 31 ++++++++++++++-- js/filters/collections/vertex.js | 31 ++++++++++++++-- js/filters/components/common.js | 51 ++++++++++++++++++++++++++ js/types/objects.js | 8 +++- 8 files changed, 194 insertions(+), 38 deletions(-) create mode 100644 js/filters/components/common.js diff --git a/js/filters/collections/cluster.js b/js/filters/collections/cluster.js index eea1671..b2f72dd 100644 --- a/js/filters/collections/cluster.js +++ b/js/filters/collections/cluster.js @@ -1,3 +1,8 @@ +import { + checkboxLogic, + objectSatisfiesCheckbox, +} from "../components/checkbox.js"; +import { buildCollectionCheckboxes } from "../components/common.js"; import { addCollectionTitle, collectionFilterContainer, @@ -5,7 +10,7 @@ import { import { magnitudeRangeLogic, RangeComponent } from "../components/range.js"; import { rangeLogic } from "../components/range.js"; -function renderClusterFilters() { +function renderClusterFilters(viewObjects) { const container = collectionFilterContainer(); const title = addCollectionTitle("Cluster"); container.appendChild(title); @@ -13,6 +18,12 @@ function renderClusterFilters() { const position = new RangeComponent("position", "position", "mm"); const energy = new RangeComponent("energy", "energy", "GeV"); + const [collectionNamesContainer, collectionCheckboxes] = + buildCollectionCheckboxes( + viewObjects.datatypes["edm4hep::Cluster"].collection + ); + + container.appendChild(collectionNamesContainer); container.appendChild(position.render()); container.appendChild(energy.render()); @@ -21,13 +32,14 @@ function renderClusterFilters() { filters: { position, energy, + collectionCheckboxes, }, }; } -export function initClusterFilters(parentContainer) { - const { container, filters } = renderClusterFilters(); - const { position, energy } = filters; +export function initClusterFilters(parentContainer, viewObjects) { + const { container, filters } = renderClusterFilters(viewObjects); + const { position, energy, collectionCheckboxes } = filters; parentContainer.appendChild(container); @@ -43,6 +55,17 @@ export function initClusterFilters(parentContainer) { return false; } + if ( + !objectSatisfiesCheckbox( + object, + collectionCheckboxes, + "collectionName", + checkboxLogic + ) + ) { + return false; + } + return true; }; diff --git a/js/filters/collections/mcparticle.js b/js/filters/collections/mcparticle.js index dd5d965..98d867d 100644 --- a/js/filters/collections/mcparticle.js +++ b/js/filters/collections/mcparticle.js @@ -13,6 +13,10 @@ import { createCollectionSubtitle, createSubContainer, } from "../components/lib.js"; +import { + buildCollectionCheckboxes, + filterOutByNormalCheckboxes, +} from "../components/common.js"; function renderMCParticleFilters(viewObjects) { const container = collectionFilterContainer(); @@ -75,8 +79,15 @@ function renderMCParticleFilters(viewObjects) { }); generatorStatusContainer.appendChild(genStatusCheckboxesContainer); + const [collectionNamesContainer, collectionCheckboxes] = + buildCollectionCheckboxes( + viewObjects.datatypes["edm4hep::MCParticle"].collection + ); + checkboxes.collectionNames = collectionCheckboxes; + container.appendChild(simStatusContainer); container.appendChild(generatorStatusContainer); + container.appendChild(collectionNamesContainer); return { container, @@ -101,7 +112,7 @@ export function initMCParticleFilters(parentContainer, viewObjects) { } } - const { simStatus, generatorStatus } = checkboxes; + const { simStatus, generatorStatus, collectionNames } = checkboxes; const someSimStatusCheckbox = objectSatisfiesCheckbox( object, @@ -109,14 +120,12 @@ export function initMCParticleFilters(parentContainer, viewObjects) { "simulatorStatus", bitfieldCheckboxLogic ); - const someGenStatusCheckbox = objectSatisfiesCheckbox( - object, + const normalCheckboxes = filterOutByNormalCheckboxes(object, [ generatorStatus, - "generatorStatus", - checkboxLogic - ); + collectionNames, + ]); - return someSimStatusCheckbox && someGenStatusCheckbox; + return someSimStatusCheckbox && normalCheckboxes; }; return criteriaFunction; diff --git a/js/filters/collections/particleid.js b/js/filters/collections/particleid.js index 5af9546..2b1aea8 100644 --- a/js/filters/collections/particleid.js +++ b/js/filters/collections/particleid.js @@ -3,6 +3,10 @@ import { checkboxLogic, objectSatisfiesCheckbox, } from "../components/checkbox.js"; +import { + buildCollectionCheckboxes, + filterOutByNormalCheckboxes, +} from "../components/common.js"; import { addCollectionTitle, collectionFilterContainer, @@ -72,9 +76,16 @@ function renderParticleIdFilters(viewObjects) { }); algorithmTypeContainer.appendChild(algorithmTypeCheckboxesContainer); + const [collectionNamesContainer, collectionCheckboxes] = + buildCollectionCheckboxes( + viewObjects.datatypes["edm4hep::ParticleID"].collection + ); + checkboxes.collectionNames = collectionCheckboxes; + container.appendChild(typeContainer); container.appendChild(pdgContainer); container.appendChild(algorithmTypeContainer); + container.appendChild(collectionNamesContainer); return { container, @@ -91,19 +102,7 @@ export function initParticleIdFilters(parentContainer, viewObjects) { parentContainer.appendChild(container); const criteriaFunction = (particleId) => { - let satisfies = true; - - Object.values(checkboxes).forEach((checkboxes) => { - const res = objectSatisfiesCheckbox( - particleId, - checkboxes, - checkboxes[0].propertyName, - checkboxLogic - ); - satisfies = satisfies && res; - }); - - return satisfies; + return filterOutByNormalCheckboxes(particleId, Object.values(checkboxes)); }; return criteriaFunction; diff --git a/js/filters/collections/recoparticle.js b/js/filters/collections/recoparticle.js index 2ce9472..1c2a75c 100644 --- a/js/filters/collections/recoparticle.js +++ b/js/filters/collections/recoparticle.js @@ -1,3 +1,8 @@ +import { + checkboxLogic, + objectSatisfiesCheckbox, +} from "../components/checkbox.js"; +import { buildCollectionCheckboxes } from "../components/common.js"; import { addCollectionTitle, collectionFilterContainer, @@ -5,7 +10,7 @@ import { import { RangeComponent } from "../components/range.js"; import { rangeLogic } from "../components/range.js"; -function renderRecoParticleFilters() { +function renderRecoParticleFilters(viewObjects) { const container = collectionFilterContainer(); const title = addCollectionTitle("Reconstructed Particle"); container.appendChild(title); @@ -20,17 +25,25 @@ function renderRecoParticleFilters() { container.appendChild(rangeFilter.render()); }); + const [collectionNamesContainer, collectionCheckboxes] = + buildCollectionCheckboxes( + viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection + ); + + container.appendChild(collectionNamesContainer); + return { container, filters: { range, + collectionCheckboxes, }, }; } -export function initRecoParticleFilters(parentContainer) { - const { container, filters } = renderRecoParticleFilters(); - const { range } = filters; +export function initRecoParticleFilters(parentContainer, viewObjects) { + const { container, filters } = renderRecoParticleFilters(viewObjects); + const { range, collectionCheckboxes } = filters; parentContainer.appendChild(container); @@ -43,6 +56,17 @@ export function initRecoParticleFilters(parentContainer) { } } + if ( + !objectSatisfiesCheckbox( + object, + collectionCheckboxes, + "collectionName", + checkboxLogic + ) + ) { + return false; + } + return true; }; diff --git a/js/filters/collections/track.js b/js/filters/collections/track.js index 12b2219..91582dc 100644 --- a/js/filters/collections/track.js +++ b/js/filters/collections/track.js @@ -1,10 +1,15 @@ +import { + checkboxLogic, + objectSatisfiesCheckbox, +} from "../components/checkbox.js"; +import { buildCollectionCheckboxes } from "../components/common.js"; import { addCollectionTitle, collectionFilterContainer, } from "../components/lib.js"; import { RangeComponent, rangeLogic } from "../components/range.js"; -function renderTrackFilters() { +function renderTrackFilters(viewObjects) { const container = collectionFilterContainer(); const title = addCollectionTitle("Track"); container.appendChild(title); @@ -13,17 +18,24 @@ function renderTrackFilters() { container.appendChild(chiNdf.render()); + const [collectionNamesContainer, collectionCheckboxes] = + buildCollectionCheckboxes( + viewObjects.datatypes["edm4hep::Track"].collection + ); + container.appendChild(collectionNamesContainer); + return { container, filters: { chiNdf, + collectionCheckboxes, }, }; } -export function initTrackFilters(parentContainer) { - const { container, filters } = renderTrackFilters(); - const { chiNdf } = filters; +export function initTrackFilters(parentContainer, viewObjects) { + const { container, filters } = renderTrackFilters(viewObjects); + const { chiNdf, collectionCheckboxes } = filters; parentContainer.appendChild(container); @@ -34,6 +46,17 @@ export function initTrackFilters(parentContainer) { return false; } + if ( + !objectSatisfiesCheckbox( + object, + collectionCheckboxes, + "collectionName", + checkboxLogic + ) + ) { + return false; + } + return true; }; diff --git a/js/filters/collections/vertex.js b/js/filters/collections/vertex.js index 121f877..71b246e 100644 --- a/js/filters/collections/vertex.js +++ b/js/filters/collections/vertex.js @@ -1,10 +1,15 @@ +import { + checkboxLogic, + objectSatisfiesCheckbox, +} from "../components/checkbox.js"; +import { buildCollectionCheckboxes } from "../components/common.js"; import { addCollectionTitle, collectionFilterContainer, } from "../components/lib.js"; import { magnitudeRangeLogic, RangeComponent } from "../components/range.js"; -function renderVertexFilters() { +function renderVertexFilters(viewObjects) { const container = collectionFilterContainer(); const title = addCollectionTitle("Vertex"); container.appendChild(title); @@ -13,17 +18,24 @@ function renderVertexFilters() { container.appendChild(position.render()); + const [collectionNamesContainer, collectionCheckboxes] = + buildCollectionCheckboxes( + viewObjects.datatypes["edm4hep::Vertex"].collection + ); + container.appendChild(collectionNamesContainer); + return { container, filters: { position, + collectionCheckboxes, }, }; } -export function initVertexFilters(parentContainer) { - const { container, filters } = renderVertexFilters(); - const { position } = filters; +export function initVertexFilters(parentContainer, viewObjects) { + const { container, filters } = renderVertexFilters(viewObjects); + const { position, collectionCheckboxes } = filters; parentContainer.appendChild(container); @@ -34,6 +46,17 @@ export function initVertexFilters(parentContainer) { return false; } + if ( + !objectSatisfiesCheckbox( + object, + collectionCheckboxes, + "collectionName", + checkboxLogic + ) + ) { + return false; + } + return true; }; diff --git a/js/filters/components/common.js b/js/filters/components/common.js new file mode 100644 index 0000000..cbe5584 --- /dev/null +++ b/js/filters/components/common.js @@ -0,0 +1,51 @@ +import { + CheckboxComponent, + checkboxLogic, + objectSatisfiesCheckbox, +} from "./checkbox.js"; +import { + createCheckboxContainer, + createCollectionSubtitle, + createSubContainer, +} from "./lib.js"; + +export function buildCollectionCheckboxes(collection) { + const container = createSubContainer(); + const title = createCollectionSubtitle("Collection"); + container.appendChild(title); + const checkboxesContainer = createCheckboxContainer(); + + const checkboxes = []; + const collections = new Set(); + collection.forEach((object) => collections.add(object.collectionName)); + + collections.forEach((collectionName) => { + const checkbox = new CheckboxComponent( + "collectionName", + collectionName, + collectionName, + true + ); + checkboxes.push(checkbox); + checkboxesContainer.appendChild(checkbox.render()); + }); + container.appendChild(checkboxesContainer); + + return [container, checkboxes]; +} + +export function filterOutByNormalCheckboxes(object, checkboxGroup) { + let satisfies = true; + + Object.values(checkboxGroup).forEach((checkboxes) => { + const res = objectSatisfiesCheckbox( + object, + checkboxes, + checkboxes[0].propertyName, + checkboxLogic + ); + satisfies = satisfies && res; + }); + + return satisfies; +} diff --git a/js/types/objects.js b/js/types/objects.js index a7cbf95..a71de3f 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -306,13 +306,17 @@ class Track extends EDMObject { this.chiNdf = chiNdf; lines.push("chi2/ndf = " + chiNdf); lines.push("dEdx = " + this.dEdx); - const trackerHitsCount = this.oneToManyRelations["trackerHits"].length; + const trackerHitsCount = this.trackerHitsCount; lines.push("tracker hits: " + trackerHitsCount); addLinesToBox(lines, box, nextY); } - static setup(trackCollection) {} + static setup(trackCollection) { + trackCollection.forEach((track) => { + track.trackerHitsCount = track.oneToManyRelations["trackerHits"].length; + }); + } } class ParticleID extends EDMObject { From 53c9f820afaa981db55e3ec2ca5e03a7d81047f2 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 12 Aug 2024 17:58:12 -0500 Subject: [PATCH 2/9] add buttons to choose all/clear all collections names --- css/filter.css | 17 +++++++++++++++++ js/filters/components/checkbox.js | 4 ++++ js/filters/components/common.js | 20 +++++++++++++++++++- js/filters/components/lib.js | 7 +++++++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/css/filter.css b/css/filter.css index 91907f3..3fb1a8e 100644 --- a/css/filter.css +++ b/css/filter.css @@ -158,3 +158,20 @@ .filter-checkbox { margin: 2px; } + +.collection-checkboxes-handler { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.checkbox-button { + padding: 4px; + margin: 0 5px; + border: 1px solid #000; + border-radius: 5px; +} + +.checkbox-button:hover { + background-color: #c5c5c5; +} diff --git a/js/filters/components/checkbox.js b/js/filters/components/checkbox.js index 7caaf09..8c6fee2 100644 --- a/js/filters/components/checkbox.js +++ b/js/filters/components/checkbox.js @@ -37,6 +37,10 @@ export class CheckboxComponent { return div; } + checked(value) { + this.checkbox.checked = value; + } + getValues() { return { checked: this.checkbox.checked, diff --git a/js/filters/components/common.js b/js/filters/components/common.js index cbe5584..7c56573 100644 --- a/js/filters/components/common.js +++ b/js/filters/components/common.js @@ -4,6 +4,7 @@ import { objectSatisfiesCheckbox, } from "./checkbox.js"; import { + createButtonForCheckboxes, createCheckboxContainer, createCollectionSubtitle, createSubContainer, @@ -11,8 +12,17 @@ import { export function buildCollectionCheckboxes(collection) { const container = createSubContainer(); + const div = document.createElement("div"); + div.classList.add("collection-checkboxes-handler"); const title = createCollectionSubtitle("Collection"); - container.appendChild(title); + const buttonsDiv = document.createElement("div"); + const selectAll = createButtonForCheckboxes("Select all"); + const clearAll = createButtonForCheckboxes("Clear all"); + div.appendChild(title); + buttonsDiv.appendChild(selectAll); + buttonsDiv.appendChild(clearAll); + div.appendChild(buttonsDiv); + container.appendChild(div); const checkboxesContainer = createCheckboxContainer(); const checkboxes = []; @@ -31,6 +41,14 @@ export function buildCollectionCheckboxes(collection) { }); container.appendChild(checkboxesContainer); + selectAll.addEventListener("click", () => { + checkboxes.forEach((checkbox) => checkbox.checked(true)); + }); + + clearAll.addEventListener("click", () => { + checkboxes.forEach((checkbox) => checkbox.checked(false)); + }); + return [container, checkboxes]; } diff --git a/js/filters/components/lib.js b/js/filters/components/lib.js index b6451a5..a1c18aa 100644 --- a/js/filters/components/lib.js +++ b/js/filters/components/lib.js @@ -29,3 +29,10 @@ export function createCheckboxContainer() { container.classList.add("filter-checkbox-container"); return container; } + +export function createButtonForCheckboxes(text) { + const button = document.createElement("button"); + button.classList.add("checkbox-button"); + button.innerText = text; + return button; +} From 6953f08a69cc9edc1026bf1a8376698d8a2e8cb3 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 13 Aug 2024 17:12:29 -0500 Subject: [PATCH 3/9] auto-check for valid checkboxes --- js/filters/collections/mcparticle.js | 18 +++++++++++------- js/filters/components/common.js | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/js/filters/collections/mcparticle.js b/js/filters/collections/mcparticle.js index 98d867d..5bdeac2 100644 --- a/js/filters/collections/mcparticle.js +++ b/js/filters/collections/mcparticle.js @@ -1,6 +1,5 @@ import { CheckboxComponent, - checkboxLogic, bitfieldCheckboxLogic, objectSatisfiesCheckbox, } from "../components/checkbox.js"; @@ -46,14 +45,18 @@ function renderMCParticleFilters(viewObjects) { simStatusContainer.appendChild(simStatusTitle); const simStatusCheckboxesContainer = createCheckboxContainer(); - Object.keys(SimStatusBitFieldDisplayValues).forEach((status) => { - const checkbox = new CheckboxComponent( - "simulatorStatus", - status, - SimStatusBitFieldDisplayValues[status] - ); + Object.entries(SimStatusBitFieldDisplayValues).forEach(([status, value]) => { + const checkbox = new CheckboxComponent("simulatorStatus", status, value); checkboxes.simStatus.push(checkbox); simStatusCheckboxesContainer.appendChild(checkbox.render()); + + viewObjects.datatypes["edm4hep::MCParticle"].collection.forEach( + (mcparticle) => { + if (bitfieldCheckboxLogic(value, mcparticle, "simulatorStatus")) { + checkbox.checked(true); + } + } + ); }); simStatusContainer.appendChild(simStatusCheckboxesContainer); @@ -76,6 +79,7 @@ function renderMCParticleFilters(viewObjects) { ); checkboxes.generatorStatus.push(checkbox); genStatusCheckboxesContainer.appendChild(checkbox.render()); + checkbox.checked(true); }); generatorStatusContainer.appendChild(genStatusCheckboxesContainer); diff --git a/js/filters/components/common.js b/js/filters/components/common.js index 7c56573..72c62c6 100644 --- a/js/filters/components/common.js +++ b/js/filters/components/common.js @@ -38,6 +38,7 @@ export function buildCollectionCheckboxes(collection) { ); checkboxes.push(checkbox); checkboxesContainer.appendChild(checkbox.render()); + checkbox.checked(true); }); container.appendChild(checkboxesContainer); From 22dceaab509973601fabd1fcdbf527191c041ebc Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 13 Aug 2024 17:36:33 -0500 Subject: [PATCH 4/9] don't apply filtering if no input changed --- js/filters/components/checkbox.js | 2 ++ js/filters/components/common.js | 10 ++++++++-- js/filters/components/range.js | 1 + js/filters/filter.js | 23 +++++++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/js/filters/components/checkbox.js b/js/filters/components/checkbox.js index 8c6fee2..fdb72b6 100644 --- a/js/filters/components/checkbox.js +++ b/js/filters/components/checkbox.js @@ -8,6 +8,8 @@ const createCheckbox = () => { const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.classList.add("filter-checkbox"); + checkbox.classList.add("filter-input-checkbox"); + return checkbox; }; diff --git a/js/filters/components/common.js b/js/filters/components/common.js index 72c62c6..5b101e2 100644 --- a/js/filters/components/common.js +++ b/js/filters/components/common.js @@ -43,11 +43,17 @@ export function buildCollectionCheckboxes(collection) { container.appendChild(checkboxesContainer); selectAll.addEventListener("click", () => { - checkboxes.forEach((checkbox) => checkbox.checked(true)); + checkboxes.forEach((checkbox) => { + checkbox.checked(true); + checkbox.checkbox.dispatchEvent(new Event("change")); + }); }); clearAll.addEventListener("click", () => { - checkboxes.forEach((checkbox) => checkbox.checked(false)); + checkboxes.forEach((checkbox) => { + checkbox.checked(false); + checkbox.checkbox.dispatchEvent(new Event("change")); + }); }); return [container, checkboxes]; diff --git a/js/filters/components/range.js b/js/filters/components/range.js index 5fdd5e5..fe18aa0 100644 --- a/js/filters/components/range.js +++ b/js/filters/components/range.js @@ -3,6 +3,7 @@ const createInput = (placeholder) => { input.type = "number"; input.placeholder = placeholder; input.classList.add("range-input"); + input.classList.add("filter-input-range"); return input; }; diff --git a/js/filters/filter.js b/js/filters/filter.js index be96356..fa86fe7 100644 --- a/js/filters/filter.js +++ b/js/filters/filter.js @@ -47,6 +47,8 @@ export function initFilters( ) { const criteriaFunctions = {}; + let someInputChanged = false; + const resetFiltersContent = () => { const content = document.getElementById("filters-content"); content.replaceChildren(); @@ -69,11 +71,32 @@ export function initFilters( const filterOutCheckbox = document.getElementById("invert-filter"); filterOutCheckbox.checked = false; + + const allCheckboxes = document.getElementsByClassName( + "filter-input-checkbox" + ); + + for (const input of allCheckboxes) { + input.addEventListener("change", () => { + someInputChanged = true; + }); + } + + const allInputs = document.getElementsByClassName("filter-input-range"); + + for (const input of allInputs) { + input.addEventListener("input", () => { + someInputChanged = true; + }); + } }; resetFiltersContent(); filters.apply = async () => { + if (!someInputChanged) { + return; + } const filterOutValue = document.getElementById("invert-filter").checked; const ids = filterOut( viewObjects, From e61673bbb86317ad4d223f336a8e57b85fd49a6b Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Wed, 14 Aug 2024 17:40:50 -0500 Subject: [PATCH 5/9] not alter view if no filter changed + new checboxes logic: filter out everythin if no checkbox checked --- js/filters/collections/mcparticle.js | 16 ++++++- js/filters/collections/recoparticle.js | 14 ++++-- js/filters/components/checkbox.js | 14 +----- js/filters/components/common.js | 2 - js/filters/filter.js | 62 ++++++++++++++++++++++---- 5 files changed, 81 insertions(+), 27 deletions(-) diff --git a/js/filters/collections/mcparticle.js b/js/filters/collections/mcparticle.js index 5bdeac2..f0b4ca7 100644 --- a/js/filters/collections/mcparticle.js +++ b/js/filters/collections/mcparticle.js @@ -118,6 +118,16 @@ export function initMCParticleFilters(parentContainer, viewObjects) { const { simStatus, generatorStatus, collectionNames } = checkboxes; + let areSimStatusChecked = false; + + simStatus.forEach((checkbox) => { + const { checked } = checkbox.getValues(); + + if (checked) { + areSimStatusChecked = true; + } + }); + const someSimStatusCheckbox = objectSatisfiesCheckbox( object, simStatus, @@ -129,7 +139,11 @@ export function initMCParticleFilters(parentContainer, viewObjects) { collectionNames, ]); - return someSimStatusCheckbox && normalCheckboxes; + if (areSimStatusChecked) { + return someSimStatusCheckbox && normalCheckboxes; + } else { + return normalCheckboxes; + } }; return criteriaFunction; diff --git a/js/filters/collections/recoparticle.js b/js/filters/collections/recoparticle.js index 1c2a75c..0e503ee 100644 --- a/js/filters/collections/recoparticle.js +++ b/js/filters/collections/recoparticle.js @@ -7,7 +7,7 @@ import { addCollectionTitle, collectionFilterContainer, } from "../components/lib.js"; -import { RangeComponent } from "../components/range.js"; +import { magnitudeRangeLogic, RangeComponent } from "../components/range.js"; import { rangeLogic } from "../components/range.js"; function renderRecoParticleFilters(viewObjects) { @@ -19,12 +19,14 @@ function renderRecoParticleFilters(viewObjects) { const charge = new RangeComponent("charge", "charge", "e"); const momentum = new RangeComponent("momentum", "momentum", "GeV"); - const range = [energy, charge, momentum]; + const range = [energy, charge]; range.forEach((rangeFilter) => { container.appendChild(rangeFilter.render()); }); + container.appendChild(momentum.render()); + const [collectionNamesContainer, collectionCheckboxes] = buildCollectionCheckboxes( viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection @@ -37,13 +39,14 @@ function renderRecoParticleFilters(viewObjects) { filters: { range, collectionCheckboxes, + momentum, }, }; } export function initRecoParticleFilters(parentContainer, viewObjects) { const { container, filters } = renderRecoParticleFilters(viewObjects); - const { range, collectionCheckboxes } = filters; + const { range, collectionCheckboxes, momentum } = filters; parentContainer.appendChild(container); @@ -56,6 +59,11 @@ export function initRecoParticleFilters(parentContainer, viewObjects) { } } + const { min, max } = momentum.getValues(); + if (!magnitudeRangeLogic(min, max, object, "momentum")) { + return false; + } + if ( !objectSatisfiesCheckbox( object, diff --git a/js/filters/components/checkbox.js b/js/filters/components/checkbox.js index fdb72b6..afbf414 100644 --- a/js/filters/components/checkbox.js +++ b/js/filters/components/checkbox.js @@ -65,22 +65,10 @@ export function objectSatisfiesCheckbox( property, logicFunction ) { - const checkedBoxes = []; - for (const checkbox of checkboxes) { const { checked, value } = checkbox.getValues(); - if (checked) { - checkedBoxes.push(value); - } - } - - if (checkedBoxes.length === 0) { - return true; - } - - for (const checked of checkedBoxes) { - if (logicFunction(checked, object, property)) { + if (checked && logicFunction(value, object, property)) { return true; } } diff --git a/js/filters/components/common.js b/js/filters/components/common.js index 5b101e2..a4af841 100644 --- a/js/filters/components/common.js +++ b/js/filters/components/common.js @@ -45,14 +45,12 @@ export function buildCollectionCheckboxes(collection) { selectAll.addEventListener("click", () => { checkboxes.forEach((checkbox) => { checkbox.checked(true); - checkbox.checkbox.dispatchEvent(new Event("change")); }); }); clearAll.addEventListener("click", () => { checkboxes.forEach((checkbox) => { checkbox.checked(false); - checkbox.checkbox.dispatchEvent(new Event("change")); }); }); diff --git a/js/filters/filter.js b/js/filters/filter.js index fa86fe7..263fa5f 100644 --- a/js/filters/filter.js +++ b/js/filters/filter.js @@ -1,5 +1,7 @@ import { setScroll, setScrollBarsPosition } from "../draw/scroll.js"; import { copyObject } from "../lib/copy.js"; +import { checkEmptyObject } from "../lib/empty-object.js"; +import { showMessage } from "../lib/messages.js"; import { initClusterFilters } from "./collections/cluster.js"; import { initMCParticleFilters } from "./collections/mcparticle.js"; import { initParticleIdFilters } from "./collections/particleid.js"; @@ -39,6 +41,34 @@ const filters = { reset: null, }; +const filterOptionsChanged = (initialValues) => { + const allCheckboxes = document.getElementsByClassName( + "filter-input-checkbox" + ); + + for (let i = 0; i < allCheckboxes.length; i++) { + const checked = allCheckboxes[i].checked; + const initialValue = initialValues.checkboxes[i]; + + if (checked !== initialValue) { + return true; + } + } + + const allInputs = document.getElementsByClassName("filter-input-range"); + + for (let i = 0; i < allInputs.length; i++) { + const input = allInputs[i].value; + const initialInput = initialValues.range[i]; + + if (input !== initialInput) { + return true; + } + } + + return false; +}; + export function initFilters( { viewObjects, viewCurrentObjects }, collections, @@ -47,7 +77,10 @@ export function initFilters( ) { const criteriaFunctions = {}; - let someInputChanged = false; + const initialValues = { + range: [], + checkboxes: [], + }; const resetFiltersContent = () => { const content = document.getElementById("filters-content"); @@ -76,27 +109,32 @@ export function initFilters( "filter-input-checkbox" ); + initialValues.checkboxes = []; + for (const input of allCheckboxes) { - input.addEventListener("change", () => { - someInputChanged = true; - }); + const checked = input.checked; + initialValues.checkboxes.push(checked); } const allInputs = document.getElementsByClassName("filter-input-range"); + initialValues.range = []; + for (const input of allInputs) { - input.addEventListener("input", () => { - someInputChanged = true; - }); + const value = input.value; + initialValues.range.push(value); } }; resetFiltersContent(); filters.apply = async () => { - if (!someInputChanged) { + const filtersChanged = filterOptionsChanged(initialValues); + + if (!filtersChanged) { return; } + const filterOutValue = document.getElementById("invert-filter").checked; const ids = filterOut( viewObjects, @@ -104,6 +142,14 @@ export function initFilters( criteriaFunctions, filterOutValue ); + + const isEmpty = checkEmptyObject(viewCurrentObjects); + + if (isEmpty) { + showMessage("No objects satisfy the filter options"); + return; + } + reconnectFunction(viewCurrentObjects, ids); await render(viewCurrentObjects); const { x, y } = filterScroll(); From 6a7bcdbea13333b8817c037ae4d77d06053f649f Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Thu, 15 Aug 2024 10:53:07 -0500 Subject: [PATCH 6/9] fix tree views, and don't generate new connections TODO: implement in mcparticle tree --- js/filters/reconnect/mcparticletree.js | 95 +++++++++++++------------- js/filters/reconnect/tree.js | 54 ++++----------- js/views/templates/tree.js | 2 +- 3 files changed, 61 insertions(+), 90 deletions(-) diff --git a/js/filters/reconnect/mcparticletree.js b/js/filters/reconnect/mcparticletree.js index 72dd0e6..d92828f 100644 --- a/js/filters/reconnect/mcparticletree.js +++ b/js/filters/reconnect/mcparticletree.js @@ -16,38 +16,39 @@ const findDaughterRow = (object, uniqueRows, rowToIndex) => { return NaN; }; -export function reconnectMCParticleTree(viewCurrentObjects) { +export function reconnectMCParticleTree(viewCurrentObjects, ids) { const { collection, oneToMany } = viewCurrentObjects.datatypes["edm4hep::MCParticle"]; const sortedCollection = collection.sort((a, b) => a.row - b.row); - const beginRowsIndex = {}; - sortedCollection.forEach((object, index) => { - if (beginRowsIndex[object.row] === undefined) { - beginRowsIndex[object.row] = index; - } - }); + // const beginRowsIndex = {}; + // sortedCollection.forEach((object, index) => { + // if (beginRowsIndex[object.row] === undefined) { + // beginRowsIndex[object.row] = index; + // } + // }); - const rows = sortedCollection.map((object) => object.row); - const uniqueRows = [...new Set(rows)]; + // const rows = sortedCollection.map((object) => object.row); + // const uniqueRows = [...new Set(rows)]; - const rowToIndex = {}; - for (const [index, row] of uniqueRows.entries()) { - rowToIndex[row] = index; - } + // const rowToIndex = {}; + // for (const [index, row] of uniqueRows.entries()) { + // rowToIndex[row] = index; + // } - const rowToObjectsCount = {}; + // const rowToObjectsCount = {}; - sortedCollection.forEach((object) => { - if (rowToObjectsCount[object.row] === undefined) { - rowToObjectsCount[object.row] = 1; - return; - } - rowToObjectsCount[object.row] += 1; - }); + // sortedCollection.forEach((object) => { + // if (rowToObjectsCount[object.row] === undefined) { + // rowToObjectsCount[object.row] = 1; + // return; + // } + // rowToObjectsCount[object.row] += 1; + // }); for (const object of sortedCollection) { + const { oneToManyRelations } = object; object.saveRelations(); object.oneToManyRelations = { @@ -55,34 +56,34 @@ export function reconnectMCParticleTree(viewCurrentObjects) { "daughters": [], }; - const parentRow = findParentRow(object, uniqueRows, rowToIndex); - if (parentRow !== NaN) { - const beginIndex = beginRowsIndex[parentRow]; - const endIndex = beginIndex + rowToObjectsCount[parentRow]; + // const parentRow = findParentRow(object, uniqueRows, rowToIndex); + // if (parentRow !== NaN) { + // const beginIndex = beginRowsIndex[parentRow]; + // const endIndex = beginIndex + rowToObjectsCount[parentRow]; - for (let i = beginIndex; i < endIndex; i++) { - const newParentLink = new linkTypes["parents"]( - object, - sortedCollection[i] - ); - object.oneToManyRelations["parents"].push(newParentLink); - oneToMany["parents"].push(newParentLink); - } - } + // for (let i = beginIndex; i < endIndex; i++) { + // const newParentLink = new linkTypes["parents"]( + // object, + // sortedCollection[i] + // ); + // object.oneToManyRelations["parents"].push(newParentLink); + // oneToMany["parents"].push(newParentLink); + // } + // } - const daughterRow = findDaughterRow(object, uniqueRows, rowToIndex); - if (daughterRow !== NaN) { - const beginIndex = beginRowsIndex[daughterRow]; - const endIndex = beginIndex + rowToObjectsCount[daughterRow]; + // const daughterRow = findDaughterRow(object, uniqueRows, rowToIndex); + // if (daughterRow !== NaN) { + // const beginIndex = beginRowsIndex[daughterRow]; + // const endIndex = beginIndex + rowToObjectsCount[daughterRow]; - for (let i = beginIndex; i < endIndex; i++) { - const newDaughterLink = new linkTypes["daughters"]( - object, - sortedCollection[i] - ); - object.oneToManyRelations["daughters"].push(newDaughterLink); - oneToMany["daughters"].push(newDaughterLink); - } - } + // for (let i = beginIndex; i < endIndex; i++) { + // const newDaughterLink = new linkTypes["daughters"]( + // object, + // sortedCollection[i] + // ); + // object.oneToManyRelations["daughters"].push(newDaughterLink); + // oneToMany["daughters"].push(newDaughterLink); + // } + // } } } diff --git a/js/filters/reconnect/tree.js b/js/filters/reconnect/tree.js index b6df804..e876e37 100644 --- a/js/filters/reconnect/tree.js +++ b/js/filters/reconnect/tree.js @@ -1,65 +1,35 @@ import { datatypes } from "../../../output/datatypes.js"; -import { linkTypes } from "../../types/links.js"; export function reconnectTree(viewCurrentObjects, ids) { const tree = Object.entries(viewCurrentObjects.datatypes).filter( ([_, { collection }]) => collection.length !== 0 )[0]; const collectionName = tree[0]; - const { collection: unsortedCollection } = tree[1]; - const sortedCollection = unsortedCollection.sort((a, b) => a.row - b.row); - const rows = sortedCollection.map((object) => object.row); - const uniqueRows = [...new Set(rows)]; - uniqueRows.sort((a, b) => a - b); - - const beginRowsIndex = {}; - sortedCollection.forEach((object, index) => { - if (beginRowsIndex[object.row] === undefined) { - beginRowsIndex[object.row] = index; - } - }); - - const rowsCount = {}; - rows.forEach((row) => { - if (rowsCount[row] === undefined) { - rowsCount[row] = 1; - return; - } - rowsCount[row] += 1; - }); + const { collection } = tree[1]; // Assuming al trees are oneToManyRelations const relationName = datatypes[collectionName].oneToManyRelations.filter( ({ type }) => type === collectionName )[0].name; - const relationClass = linkTypes[relationName]; - for (const object of sortedCollection) { + for (const object of collection) { + const { oneToManyRelations } = object; + const oldRelations = oneToManyRelations[relationName]; object.saveRelations(); object.oneToManyRelations = { [relationName]: [], }; - const objectRow = object.row; - const nextRow = objectRow + 1; - - if (beginRowsIndex[nextRow] !== undefined) { - const beginIndex = beginRowsIndex[nextRow]; - const count = rowsCount[nextRow]; - const endIndex = beginIndex + count; - - for (let i = beginIndex; i < endIndex; i++) { - const daughter = sortedCollection[i]; - const daughterId = `${daughter.index}-${daughter.collectionId}`; + for (const relation of oldRelations) { + const child = relation.to; + const childIndex = `${child.index}-${child.collectionId}`; - if (ids.has(daughterId)) { - const relation = new relationClass(object, daughter); - object.oneToManyRelations[relationName].push(relation); - viewCurrentObjects.datatypes[collectionName].oneToMany[ - relationName - ].push(relation); - } + if (ids.has(childIndex)) { + object.oneToManyRelations[relationName].push(relation); + viewCurrentObjects.datatypes[collectionName].oneToMany[ + relationName + ].push(relation); } } } diff --git a/js/views/templates/tree.js b/js/views/templates/tree.js index 16275be..ad5553a 100644 --- a/js/views/templates/tree.js +++ b/js/views/templates/tree.js @@ -98,7 +98,7 @@ export function buildTree(collection, relationOfReference) { matrix.forEach((row, i) => { row.forEach((object, j) => { - const row = i + startingRow; + const row = i + startingRow - 1; const col = j; object.x = col * horizontalGap + col * boxWidth + horizontalGap; object.y = row * verticalGap + row * boxHeight + verticalGap; From dd2821065e654e4b4bac0b0c3dc0f6d7cbadd1b7 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Thu, 15 Aug 2024 15:34:11 -0500 Subject: [PATCH 7/9] reconnect mcparticle tree, go to level where objects aren't filtered --- js/filters/reconnect/mcparticletree.js | 121 +++++++++++-------------- 1 file changed, 55 insertions(+), 66 deletions(-) diff --git a/js/filters/reconnect/mcparticletree.js b/js/filters/reconnect/mcparticletree.js index d92828f..58395c2 100644 --- a/js/filters/reconnect/mcparticletree.js +++ b/js/filters/reconnect/mcparticletree.js @@ -1,89 +1,78 @@ import { linkTypes } from "../../types/links.js"; -const findParentRow = (object, uniqueRows, rowToIndex) => { - const thisRowIndex = rowToIndex[object.row]; - if (thisRowIndex > 0 && thisRowIndex < uniqueRows.length) { - return uniqueRows[thisRowIndex - 1]; +const findParticles = (otherObject, relationName, ids) => { + let oneToManyRelations; + if (otherObject.relations) { + oneToManyRelations = otherObject.relations.oneToManyRelations; + } else { + oneToManyRelations = otherObject.oneToManyRelations; } - return NaN; -}; - -const findDaughterRow = (object, uniqueRows, rowToIndex) => { - const thisRowIndex = rowToIndex[object.row]; - if (thisRowIndex >= 0 && thisRowIndex < uniqueRows.length - 1) { - return uniqueRows[thisRowIndex + 1]; - } - return NaN; -}; -export function reconnectMCParticleTree(viewCurrentObjects, ids) { - const { collection, oneToMany } = - viewCurrentObjects.datatypes["edm4hep::MCParticle"]; + const relations = oneToManyRelations[relationName]; - const sortedCollection = collection.sort((a, b) => a.row - b.row); + if (relations.length === 0) return []; - // const beginRowsIndex = {}; - // sortedCollection.forEach((object, index) => { - // if (beginRowsIndex[object.row] === undefined) { - // beginRowsIndex[object.row] = index; - // } - // }); + let hasAny = 0; - // const rows = sortedCollection.map((object) => object.row); - // const uniqueRows = [...new Set(rows)]; + relations.forEach((object) => + ids.has(`${object.index}-${object.collectionId}`) ? (hasAny += 1) : null + ); - // const rowToIndex = {}; - // for (const [index, row] of uniqueRows.entries()) { - // rowToIndex[row] = index; - // } - - // const rowToObjectsCount = {}; + return hasAny > 0 + ? relations + : relations + .map((parentLink) => findParticles(parentLink.to, relationName, ids)) + .flat(); +}; - // sortedCollection.forEach((object) => { - // if (rowToObjectsCount[object.row] === undefined) { - // rowToObjectsCount[object.row] = 1; - // return; - // } - // rowToObjectsCount[object.row] += 1; - // }); +export function reconnectMCParticleTree(viewCurrentObjects, ids) { + const { collection, oneToMany } = + viewCurrentObjects.datatypes["edm4hep::MCParticle"]; - for (const object of sortedCollection) { + for (const object of collection) { const { oneToManyRelations } = object; object.saveRelations(); + const parentRelations = oneToManyRelations["parents"]; + const daughterRelations = oneToManyRelations["daughters"]; + object.oneToManyRelations = { "parents": [], "daughters": [], }; - // const parentRow = findParentRow(object, uniqueRows, rowToIndex); - // if (parentRow !== NaN) { - // const beginIndex = beginRowsIndex[parentRow]; - // const endIndex = beginIndex + rowToObjectsCount[parentRow]; + for (const parentRelation of parentRelations) { + const parent = parentRelation.to; + const parentIndex = `${parent.index}-${parent.collectionId}`; - // for (let i = beginIndex; i < endIndex; i++) { - // const newParentLink = new linkTypes["parents"]( - // object, - // sortedCollection[i] - // ); - // object.oneToManyRelations["parents"].push(newParentLink); - // oneToMany["parents"].push(newParentLink); - // } - // } + if (ids.has(parentIndex)) { + object.oneToManyRelations["parents"].push(parentRelation); + oneToMany["parents"].push(parentRelation); + } else { + const newParents = findParticles(parent, "parents", ids); + for (const newParent of newParents) { + const link = new linkTypes["parents"](object, newParent); + object.oneToManyRelations["parents"].push(link); + oneToMany["parents"].push(link); + } + } + } - // const daughterRow = findDaughterRow(object, uniqueRows, rowToIndex); - // if (daughterRow !== NaN) { - // const beginIndex = beginRowsIndex[daughterRow]; - // const endIndex = beginIndex + rowToObjectsCount[daughterRow]; + for (const daughterRelation of daughterRelations) { + const daughter = daughterRelation.to; + const daughterIndex = `${daughter.index}-${daughter.collectionId}`; - // for (let i = beginIndex; i < endIndex; i++) { - // const newDaughterLink = new linkTypes["daughters"]( - // object, - // sortedCollection[i] - // ); - // object.oneToManyRelations["daughters"].push(newDaughterLink); - // oneToMany["daughters"].push(newDaughterLink); - // } - // } + if (ids.has(daughterIndex)) { + object.oneToManyRelations["daughters"].push(daughterRelation); + oneToMany["daughters"].push(daughterRelation); + } else { + const newDaughters = findParticles(daughter, "daughters", ids); + for (const newDaughter of newDaughters) { + const link = new linkTypes["daughters"](object, newDaughter); + object.oneToManyRelations["daughters"].push(link); + oneToMany["daughters"].push(link); + } + } + } } } From 7db7d190bf655659746e161ea484e161b8463383 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 16 Aug 2024 09:54:40 -0500 Subject: [PATCH 8/9] fix reconnect, it was working with relations instead of objects --- js/filters/reconnect/mcparticletree.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/js/filters/reconnect/mcparticletree.js b/js/filters/reconnect/mcparticletree.js index 58395c2..9561254 100644 --- a/js/filters/reconnect/mcparticletree.js +++ b/js/filters/reconnect/mcparticletree.js @@ -9,19 +9,18 @@ const findParticles = (otherObject, relationName, ids) => { } const relations = oneToManyRelations[relationName]; + const relationObjects = relations.map((relation) => relation.to); - if (relations.length === 0) return []; + if (relationObjects.length === 0) return []; - let hasAny = 0; - - relations.forEach((object) => - ids.has(`${object.index}-${object.collectionId}`) ? (hasAny += 1) : null + relationObjects.filter((object) => + ids.has(`${object.index}-${object.collectionId}`) ); - return hasAny > 0 - ? relations - : relations - .map((parentLink) => findParticles(parentLink.to, relationName, ids)) + return relationObjects.length > 0 + ? relationObjects + : relationObjects + .map((object) => findParticles(object, relationName, ids)) .flat(); }; From cf47086adbcf7680bffd269bc858b7fc39bf82f1 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 16 Aug 2024 10:19:42 -0500 Subject: [PATCH 9/9] work with valid objects that weren't filtered out --- js/filters/reconnect/mcparticletree.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/filters/reconnect/mcparticletree.js b/js/filters/reconnect/mcparticletree.js index 9561254..639ddb0 100644 --- a/js/filters/reconnect/mcparticletree.js +++ b/js/filters/reconnect/mcparticletree.js @@ -13,12 +13,12 @@ const findParticles = (otherObject, relationName, ids) => { if (relationObjects.length === 0) return []; - relationObjects.filter((object) => + const validObjects = relationObjects.filter((object) => ids.has(`${object.index}-${object.collectionId}`) ); - return relationObjects.length > 0 - ? relationObjects + return validObjects.length > 0 + ? validObjects : relationObjects .map((object) => findParticles(object, relationName, ids)) .flat();