From 8279495c02ee5118d509ee8e3252824f4ef8e310 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 2 Jul 2024 20:35:19 -0500 Subject: [PATCH] add view for cluster, track and mc with each one --- css/views.css | 20 ++++ index.html | 4 +- js/event-number.js | 4 +- js/main.js | 4 + js/types/links.js | 60 ++++++++--- js/types/load.js | 9 -- js/types/objects.js | 174 +++++++++++++++++-------------- js/views/association-view.js | 49 +++++++++ js/views/clustertree.js | 18 ++++ js/views/mcclusterassociation.js | 19 ++++ js/views/mcparticletree.js | 8 +- js/views/mcrecoassociation.js | 76 ++------------ js/views/mctrackassociation.js | 16 +++ js/views/pre-filter.js | 39 +++++++ js/views/recoparticletree.js | 84 ++------------- js/views/scrolls.js | 9 ++ js/views/tracktree.js | 18 ++++ js/views/tree.js | 72 +++++++++++++ js/views/views-dictionary.js | 73 +++++++++---- model/index.js | 2 + output/datatypes.js | 34 ++++++ 21 files changed, 521 insertions(+), 271 deletions(-) create mode 100644 js/views/association-view.js create mode 100644 js/views/clustertree.js create mode 100644 js/views/mcclusterassociation.js create mode 100644 js/views/mctrackassociation.js create mode 100644 js/views/pre-filter.js create mode 100644 js/views/scrolls.js create mode 100644 js/views/tracktree.js create mode 100644 js/views/tree.js diff --git a/css/views.css b/css/views.css index f6ebf157..86e4c0c8 100644 --- a/css/views.css +++ b/css/views.css @@ -5,6 +5,8 @@ justify-content: flex-start; align-items: center; padding: 8px; + max-height: 90px; + overflow-y: auto; } .view-button { @@ -39,3 +41,21 @@ overflow-x: hidden; width: fit-content; } + +.view-selector-menu::-webkit-scrollbar { + width: 7px; +} + +.view-selector-menu::-webkit-scrollbar-track { + background: #e1e1e1; + border-radius: 5px; +} + +.view-selector-menu::-webkit-scrollbar-thumb { + background: #afafaf; + border-radius: 5px; +} + +.view-selector-menu::-webkit-scrollbar-thumb:hover { + background: #858585; +} diff --git a/index.html b/index.html index 42e9c369..3cb6744e 100644 --- a/index.html +++ b/index.html @@ -42,7 +42,7 @@ -
+

@@ -152,7 +152,7 @@

Select a view:

-
+
diff --git a/js/event-number.js b/js/event-number.js index f14540c9..83b773ad 100644 --- a/js/event-number.js +++ b/js/event-number.js @@ -1,6 +1,6 @@ import { loadObjects } from "./types/load.js"; import { copyObject } from "./lib/copy.js"; -import { canvas, jsonData, selectedObjectTypes } from "./main.js"; +import { jsonData, selectedObjectTypes } from "./main.js"; import { objectTypes } from "./types/objects.js"; import { drawCurrentView, saveScrollLocation } from "./views/views.js"; @@ -37,7 +37,7 @@ function loadSelectedEvent() { )) { const classType = objectTypes[key]; const collection = value.collection; - classType.setup(collection, canvas); + classType.setup(collection); } copyObject(objects, currentObjects); } else { diff --git a/js/main.js b/js/main.js index bc839d64..d81ab7f7 100644 --- a/js/main.js +++ b/js/main.js @@ -16,6 +16,10 @@ const selectedObjectTypes = { "edm4hep::MCParticle", "edm4hep::ReconstructedParticle", "edm4hep::MCRecoParticleAssociation", + "edm4hep::MCRecoTrackParticleAssociation", + "edm4hep::MCRecoClusterParticleAssociation", + "edm4hep::Cluster", + "edm4hep::Track", ], }; diff --git a/js/types/links.js b/js/types/links.js index 1d83013d..524387d7 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -1,15 +1,19 @@ -export const colors = { - "daughters": "#00AA00", +const colors = { "parents": "#AA0000", + "daughters": "#00AA00", "mcreco": "#0000AA", + "tracks": "#AAAA00", + "clusters": "#00AAAA", "particles": "#AA00AA", + "mcclusters": "#AA00AA", + "mctracks": "#AA0000", }; -export function generateRandomColor() { +function generateRandomColor() { return "#" + ((0xffffff * Math.random()) << 0).toString(16).padStart(6, "0"); } -export class Link { +class Link { // we may create a specific class for each type if needed constructor(from, to) { this.from = from; @@ -109,7 +113,7 @@ export class Link { } } -export class ParentLink extends Link { +class ParentLink extends Link { constructor(from, to) { super(to, from); this.color = colors["parents"]; @@ -119,7 +123,7 @@ export class ParentLink extends Link { } } -export class DaughterLink extends Link { +class DaughterLink extends Link { constructor(from, to) { super(from, to); this.color = colors["daughters"]; @@ -129,7 +133,7 @@ export class DaughterLink extends Link { } } -export class MCRecoParticleAssociation extends Link { +class MCRecoParticleAssociation extends Link { constructor(from, to, weight) { super(from, to); this.color = colors["mcreco"]; @@ -157,19 +161,51 @@ export class MCRecoParticleAssociation extends Link { } } -export class Particles extends Link { +class Particles extends Link { constructor(from, to) { super(from, to); this.color = colors["particles"]; } } +class Clusters extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["clusters"]; + } +} + +class Tracks extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["tracks"]; + } +} + +class MCRecoTrackParticleAssociation extends Link { + constructor(from, to, weight) { + super(from, to); + this.color = colors["mctracks"]; + this.weight = weight; + } +} + +class MCRecoClusterParticleAssociation extends Link { + constructor(from, to, weight) { + super(from, to); + this.color = colors["mcclusters"]; + this.weight = weight; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, - "trackerHits": Link, - "startVertex": Link, - "particles": Particles, - "clusters": Link, "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, + "edm4hep::MCRecoClusterParticleAssociation": MCRecoClusterParticleAssociation, + "edm4hep::MCRecoTrackParticleAssociation": MCRecoTrackParticleAssociation, + "clusters": Clusters, + "tracks": Tracks, + "particles": Particles, + "startVertex": Link, }; diff --git a/js/types/load.js b/js/types/load.js index c0700bac..d2b12bd7 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -212,12 +212,3 @@ export function loadObjects(jsonData, event, objectsToLoad) { return objects; } -// console.time("load"); -// const data = loadObjects(json, 0, [ -// "edm4hep::MCParticle", -// "edm4hep::ReconstructedParticle", -// "edm4hep::Cluster", -// "edm4hep::MCRecoParticleAssociation", -// ]); -// console.timeEnd("load"); -// console.log(data); diff --git a/js/types/objects.js b/js/types/objects.js index 2196b94d..f633829c 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -6,6 +6,8 @@ class EDMObject { constructor() { this.x = NaN; this.y = NaN; + this.index = NaN; + this.collectionId = NaN; this.width = 120; this.height = 240; this.lineColor = "black"; @@ -34,81 +36,7 @@ class EDMObject { } } -export class Cluster extends EDMObject { - constructor() { - super(); - } -} - -export class ParticleID extends EDMObject { - constructor() { - super(); - } -} - -export class ReconstructedParticle extends EDMObject { - constructor() { - super(); - } - - draw(ctx) { - const boxCenterX = this.x + this.width / 2; - - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); - - const topY = this.y + 20; - const topLines = []; - topLines.push("ID: " + this.index); - const energy = parseInt(this.energy * 100) / 100; - topLines.push("e = " + energy + " GeV"); - topLines.push("c = " + this.charge + " e"); - if (Math.abs(this.charge) < 1.0 && this.charge != 0) { - if (Math.round(this.charge * 1000) === 667) { - topLines.push("q = 2/3 e"); - } - if (Math.round(this.charge * 1000) === -667) { - topLines.push("q = -2/3 e"); - } - if (Math.round(this.charge * 1000) === 333) { - topLines.push("q = 1/3 e"); - } - if (Math.round(this.charge * 1000) === -333) { - topLines.push("q = -1/3 e"); - } - } else { - topLines.push("q = " + this.charge + " e"); - } - - ctx.save(); - ctx.font = "16px sans-serif"; - for (const [i, lineText] of topLines.entries()) { - ctx.fillText( - lineText, - boxCenterX - ctx.measureText(lineText).width / 2, - topY + i * 23 - ); - } - ctx.restore(); - } - - static setup(recoCollection) {} - - static filter() {} -} - -export class Vertex extends EDMObject { - constructor() { - super(); - } -} - -export class Track extends EDMObject { - constructor() { - super(); - } -} - -export class MCParticle extends EDMObject { +class MCParticle extends EDMObject { constructor() { super(); @@ -285,11 +213,101 @@ export class MCParticle extends EDMObject { } } +class ReconstructedParticle extends EDMObject { + constructor() { + super(); + } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + + const topY = this.y + 20; + const topLines = []; + topLines.push("ID: " + this.index); + const energy = parseInt(this.energy * 100) / 100; + topLines.push("e = " + energy + " GeV"); + topLines.push("c = " + this.charge + " e"); + if (Math.abs(this.charge) < 1.0 && this.charge != 0) { + if (Math.round(this.charge * 1000) === 667) { + topLines.push("q = 2/3 e"); + } + if (Math.round(this.charge * 1000) === -667) { + topLines.push("q = -2/3 e"); + } + if (Math.round(this.charge * 1000) === 333) { + topLines.push("q = 1/3 e"); + } + if (Math.round(this.charge * 1000) === -333) { + topLines.push("q = -1/3 e"); + } + } else { + topLines.push("q = " + this.charge + " e"); + } + + ctx.save(); + ctx.font = "16px sans-serif"; + for (const [i, lineText] of topLines.entries()) { + ctx.fillText( + lineText, + boxCenterX - ctx.measureText(lineText).width / 2, + topY + i * 23 + ); + } + ctx.restore(); + } + + static setup(recoCollection) {} + + static filter() {} +} + +class Cluster extends EDMObject { + constructor() { + super(); + } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + } + + static setup(clusterCollection) {} +} + +class Track extends EDMObject { + constructor() { + super(); + } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + } + + static setup(trackCollection) {} +} + +class ParticleID extends EDMObject { + constructor() { + super(); + } +} + +class Vertex extends EDMObject { + constructor() { + super(); + } +} + export const objectTypes = { + "edm4hep::MCParticle": MCParticle, + "edm4hep::ReconstructedParticle": ReconstructedParticle, "edm4hep::Cluster": Cluster, + "edm4hep::Track": Track, "edm4hep::ParticleID": ParticleID, - "edm4hep::ReconstructedParticle": ReconstructedParticle, "edm4hep::Vertex": Vertex, - "edm4hep::Track": Track, - "edm4hep::MCParticle": MCParticle, }; diff --git a/js/views/association-view.js b/js/views/association-view.js new file mode 100644 index 00000000..998793e5 --- /dev/null +++ b/js/views/association-view.js @@ -0,0 +1,49 @@ +import { canvas } from "../main.js"; + +// List 1:1 association in a vertical list +export function buildAssociationView(viewObjects, associationName) { + const association = viewObjects.associations[associationName]; + + const fromCollection = association.map((association) => association.from); + const toCollection = association.map((association) => association.to); + + if (fromCollection.length === 0 || toCollection.length === 0) { + alert("No association found!"); + return; + } + + const fromWidth = fromCollection[0].width; + const toWidth = toCollection[0].width; + const fromHorizontalGap = 0.3 * fromWidth; + const toHorizontalGap = 0.3 * toWidth; + const gap = 2 * (fromWidth + toWidth); + const totalWidth = gap + fromWidth + toWidth; + + canvas.width = + totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + + const width = canvas.width; + + const fromHeight = fromCollection[0].height; + const toHeight = toCollection[0].height; + const fromVerticalGap = 0.3 * fromHeight; + const toVerticalGap = 0.3 * toHeight; + + const fromTotalHeight = + fromCollection.length * (fromHeight + fromVerticalGap) + fromVerticalGap; + const toTotalHeight = + toCollection.length * (toHeight + toVerticalGap) + toVerticalGap; + + canvas.height = + fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; + + for (const [index, from] of fromCollection.entries()) { + from.y = fromVerticalGap + index * (fromHeight + fromVerticalGap); + from.x = width / 2 + fromHorizontalGap; + } + + for (const [index, to] of toCollection.entries()) { + to.y = toVerticalGap + index * (toHeight + toVerticalGap); + to.x = width / 2 - toWidth - toHorizontalGap; + } +} diff --git a/js/views/clustertree.js b/js/views/clustertree.js new file mode 100644 index 00000000..f4ce1ca9 --- /dev/null +++ b/js/views/clustertree.js @@ -0,0 +1,18 @@ +import { buildTree } from "./tree.js"; +import { preFilterTree } from "./pre-filter.js"; + +export function clusterTree(viewCurrentObjects) { + const clusterCollection = + viewCurrentObjects.datatypes["edm4hep::Cluster"].collection ?? []; + + if (clusterCollection.length === 0) { + alert("No Clusters found in this event."); + return; + } + + buildTree(clusterCollection, "clusters"); +} + +export function preFilterClusterTree(currentObjects, viewObjects) { + preFilterTree(currentObjects, viewObjects, "edm4hep::Cluster", ["clusters"]); +} diff --git a/js/views/mcclusterassociation.js b/js/views/mcclusterassociation.js new file mode 100644 index 00000000..1558072c --- /dev/null +++ b/js/views/mcclusterassociation.js @@ -0,0 +1,19 @@ +import { preFilterAssociation } from "./pre-filter.js"; +import { buildAssociationView } from "./association-view.js"; + +export function mcClusterAssociation(viewObjects) { + buildAssociationView( + viewObjects, + "edm4hep::MCRecoClusterParticleAssociation" + ); +} + +export function preFilterMCCluster(currentObjects, viewObjects) { + preFilterAssociation( + currentObjects, + viewObjects, + "edm4hep::MCRecoClusterParticleAssociation", + "edm4hep::Cluster", + "edm4hep::MCParticle" + ); +} diff --git a/js/views/mcparticletree.js b/js/views/mcparticletree.js index 43d8e73d..4b702cc1 100644 --- a/js/views/mcparticletree.js +++ b/js/views/mcparticletree.js @@ -1,4 +1,5 @@ import { canvas } from "../main.js"; +import { preFilterTree } from "./pre-filter.js"; export function mcParticleTree(viewCurrentObjects) { const mcCollection = @@ -98,6 +99,9 @@ export function mcParticleTree(viewCurrentObjects) { } } -export function mcParticleTreeScroll() { - return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; +export function preFilterMCTree(currentObjects, viewObjects) { + preFilterTree(currentObjects, viewObjects, "edm4hep::MCParticle", [ + "parents", + "daughters", + ]); } diff --git a/js/views/mcrecoassociation.js b/js/views/mcrecoassociation.js index e8f19fb1..9c3b5306 100644 --- a/js/views/mcrecoassociation.js +++ b/js/views/mcrecoassociation.js @@ -1,74 +1,16 @@ -import { canvas } from "../main.js"; -import { emptyCopyObject } from "../lib/copy.js"; +import { preFilterAssociation } from "./pre-filter.js"; +import { buildAssociationView } from "./association-view.js"; export function mcRecoAssociation(viewObjects) { - const associationMCReco = - viewObjects.associations["edm4hep::MCRecoParticleAssociation"]; - - const recoCollection = associationMCReco.map( - (association) => association.from - ); - const mcCollection = associationMCReco.map((association) => association.to); - - if (mcCollection.length === 0 || recoCollection.length === 0) { - alert("No MCRecoAssociation found!"); - return; - } - - const mcWidth = mcCollection[0].width; - const recoWidth = recoCollection[0].width; - const mcHorizontalGap = 0.3 * mcWidth; - const recoHorizontalGap = 0.3 * recoWidth; - const gap = 2 * (mcWidth + recoWidth); - const totalWidth = gap + mcWidth + recoWidth; - - canvas.width = - totalWidth > window.innerWidth ? totalWidth : window.innerWidth; - - const width = canvas.width; - - const mcHeight = mcCollection[0].height; - const recoHeight = recoCollection[0].height; - const mcVerticalGap = 0.3 * mcHeight; - const recoVerticalGap = 0.3 * recoHeight; - - const mcTotalHeight = - mcCollection.length * (mcHeight + mcVerticalGap) + mcVerticalGap; - const recoTotalHeight = - recoCollection.length * (recoHeight + recoVerticalGap) + recoVerticalGap; - - canvas.height = - mcTotalHeight > recoTotalHeight ? mcTotalHeight : recoTotalHeight; - - for (const [index, mc] of mcCollection.entries()) { - mc.y = mcVerticalGap + index * (mcHeight + mcVerticalGap); - mc.x = width / 2 - mcWidth - mcHorizontalGap; - } - - for (const [index, reco] of recoCollection.entries()) { - reco.y = recoVerticalGap + index * (recoHeight + recoVerticalGap); - reco.x = width / 2 + recoHorizontalGap; - } + buildAssociationView(viewObjects, "edm4hep::MCRecoParticleAssociation"); } export function preFilterMCReco(currentObjects, viewObjects) { - emptyCopyObject(currentObjects, viewObjects); - - const associationMCReco = - currentObjects.associations["edm4hep::MCRecoParticleAssociation"]; - - const recoCollection = associationMCReco.map( - (association) => association.from + preFilterAssociation( + currentObjects, + viewObjects, + "edm4hep::MCRecoParticleAssociation", + "edm4hep::ReconstructedParticle", + "edm4hep::MCParticle" ); - const mcCollection = associationMCReco.map((association) => association.to); - - viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection = - recoCollection; - - mcCollection.forEach((mc) => { - viewObjects.datatypes["edm4hep::MCParticle"].collection.push(mc); - }); - - viewObjects.associations["edm4hep::MCRecoParticleAssociation"] = - associationMCReco; } diff --git a/js/views/mctrackassociation.js b/js/views/mctrackassociation.js new file mode 100644 index 00000000..f9d6bde0 --- /dev/null +++ b/js/views/mctrackassociation.js @@ -0,0 +1,16 @@ +import { preFilterAssociation } from "./pre-filter.js"; +import { buildAssociationView } from "./association-view.js"; + +export function mcTrackAssociation(viewObjects) { + buildAssociationView(viewObjects, "edm4hep::MCRecoTrackParticleAssociation"); +} + +export function preFilterMCTrack(currentObjects, viewObjects) { + preFilterAssociation( + currentObjects, + viewObjects, + "edm4hep::MCRecoTrackParticleAssociation", + "edm4hep::Track", + "edm4hep::MCParticle" + ); +} diff --git a/js/views/pre-filter.js b/js/views/pre-filter.js new file mode 100644 index 00000000..8334ee6b --- /dev/null +++ b/js/views/pre-filter.js @@ -0,0 +1,39 @@ +import { emptyCopyObject } from "../lib/copy.js"; + +export function preFilterAssociation( + currentObjects, + viewObjects, + associationName, + fromCollectionName, + toCollectionName +) { + emptyCopyObject(currentObjects, viewObjects); + + const association = currentObjects.associations[associationName]; + + const fromCollection = association.map((association) => association.from); + + const toCollection = association.map((association) => association.to); + + viewObjects.datatypes[fromCollectionName].collection = fromCollection; + + viewObjects.datatypes[toCollectionName].collection = toCollection; + + viewObjects.associations[associationName] = association; +} + +export function preFilterTree( + currentObjects, + viewObjects, + collectionName, + relationsNames +) { + emptyCopyObject(currentObjects, viewObjects); + viewObjects.datatypes[collectionName].collection = + currentObjects.datatypes[collectionName].collection; + + relationsNames.forEach((relationName) => { + viewObjects.datatypes[collectionName].oneToMany[relationName] = + currentObjects.datatypes[collectionName].oneToMany[relationName]; + }); +} diff --git a/js/views/recoparticletree.js b/js/views/recoparticletree.js index e72b3c96..26a73e32 100644 --- a/js/views/recoparticletree.js +++ b/js/views/recoparticletree.js @@ -1,4 +1,5 @@ -import { canvas } from "../main.js"; +import { buildTree } from "./tree.js"; +import { preFilterTree } from "./pre-filter.js"; export function recoParticleTree(viewCurrentObjects) { const recoCollection = @@ -9,82 +10,11 @@ export function recoParticleTree(viewCurrentObjects) { alert("No ReconstructedParticles found in this event."); } - const nodes = new Set(); - const children = new Set(); - - for (const recoParticle of recoCollection) { - const particles = recoParticle.oneToManyRelations["particles"].map( - (link) => link.to - ); - nodes.add(`${recoParticle.index}-${recoParticle.collectionId}`); - for (const recoParticleChild of particles) { - children.add( - `${recoParticleChild.index}-${recoParticleChild.collectionId}` - ); - } - } - - const rootNodesIds = nodes.difference(children); - const rootNodes = []; - - recoCollection.forEach((recoParticle) => { - if ( - rootNodesIds.has(`${recoParticle.index}-${recoParticle.collectionId}`) - ) { - rootNodes.push(recoParticle); - } - }); - - rootNodes.forEach((rootNode) => { - const stack = [[rootNode, 0]]; - - while (stack.length > 0) { - const [node, row] = stack.pop(); - const id = `${node.index}-${node.collectionId}`; - if (nodes.has(id)) { - nodes.delete(id); - node.row = row; - - const particles = node.oneToManyRelations["particles"]; - - particles.forEach((link) => { - stack.push([link.to, row + 1]); - }); - } - } - }); - - const horizontalGap = recoCollection[0].width * 0.4; - const verticalGap = recoCollection[0].height * 0.3; - const boxWidth = recoCollection[0].width; - const boxHeight = recoCollection[0].height; - - const matrix = []; - - recoCollection.forEach((recoParticle) => { - const row = recoParticle.row; - if (matrix[row] === undefined) { - matrix[row] = []; - } - matrix[row].push(recoParticle); - }); - - matrix.forEach((row, i) => { - row.forEach((recoParticle, j) => { - recoParticle.x = j * horizontalGap + j * boxWidth + horizontalGap; - recoParticle.y = i * verticalGap + i * boxHeight + verticalGap; - }); - }); - - canvas.width = - boxWidth * matrix[0].length + horizontalGap * (matrix[0].length + 1); - canvas.height = - boxHeight * (matrix.length + 1) + verticalGap * (matrix.length + 2); + buildTree(recoCollection, "particles"); } -export function recoParticleTreeScroll() { - return { - x: 0, - y: 0, - }; +export function preFilterRecoTree(currentObjects, viewObjects) { + preFilterTree(currentObjects, viewObjects, "edm4hep::ReconstructedParticle", [ + "particles", + ]); } diff --git a/js/views/scrolls.js b/js/views/scrolls.js new file mode 100644 index 00000000..3624add2 --- /dev/null +++ b/js/views/scrolls.js @@ -0,0 +1,9 @@ +import { canvas } from "../main.js"; + +export function scrollTopCenter() { + return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; +} + +export function scrollTopLeft() { + return { x: 0, y: 0 }; +} diff --git a/js/views/tracktree.js b/js/views/tracktree.js new file mode 100644 index 00000000..523441b8 --- /dev/null +++ b/js/views/tracktree.js @@ -0,0 +1,18 @@ +import { buildTree } from "./tree.js"; +import { preFilterTree } from "./pre-filter.js"; + +export function trackTree(viewCurrentObjects) { + const trackCollection = + viewCurrentObjects.datatypes["edm4hep::Track"].collection ?? []; + + if (trackCollection.length === 0) { + alert("No Tracks found in this event."); + return; + } + + buildTree(trackCollection, "tracks"); +} + +export function preFilterTrackTree(currentObjects, viewObjects) { + preFilterTree(currentObjects, viewObjects, "edm4hep::Track", ["tracks"]); +} diff --git a/js/views/tree.js b/js/views/tree.js new file mode 100644 index 00000000..e57f6ffc --- /dev/null +++ b/js/views/tree.js @@ -0,0 +1,72 @@ +import { canvas } from "../main.js"; + +// All particles that are related to itself have an one to many relation +export function buildTree(collection, relationOfReference) { + const nodes = new Set(); + const children = new Set(); + + for (const object of collection) { + const objects = object.oneToManyRelations[relationOfReference].map( + (link) => link.to + ); + nodes.add(`${object.index}-${object.collectionId}`); + for (const childObject of objects) { + children.add(`${childObject.index}-${childObject.collectionId}`); + } + } + + const rootNodesIds = nodes.difference(children); + const rootNodes = []; + + collection.forEach((object) => { + if (rootNodesIds.has(`${object.index}-${object.collectionId}`)) { + rootNodes.push(object); + } + }); + + rootNodes.forEach((rootNode) => { + const stack = [[rootNode, 0]]; + + while (stack.length > 0) { + const [node, row] = stack.pop(); + const id = `${node.index}-${node.collectionId}`; + if (nodes.has(id)) { + nodes.delete(id); + node.row = row; + + const childObjectLinks = node.oneToManyRelations[relationOfReference]; + + childObjectLinks.forEach((link) => { + stack.push([link.to, row + 1]); + }); + } + } + }); + + const horizontalGap = collection[0].width * 0.4; + const verticalGap = collection[0].height * 0.3; + const boxWidth = collection[0].width; + const boxHeight = collection[0].height; + + const matrix = []; + + collection.forEach((object) => { + const row = object.row; + if (matrix[row] === undefined) { + matrix[row] = []; + } + matrix[row].push(object); + }); + + matrix.forEach((row, i) => { + row.forEach((object, j) => { + object.x = j * horizontalGap + j * boxWidth + horizontalGap; + object.y = i * verticalGap + i * boxHeight + verticalGap; + }); + }); + + canvas.width = + boxWidth * matrix[0].length + horizontalGap * (matrix[0].length + 1); + canvas.height = + boxHeight * (matrix.length + 1) + verticalGap * (matrix.length + 2); +} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 86d00b70..a5783d91 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -1,40 +1,69 @@ -import { mcParticleTree, mcParticleTreeScroll } from "./mcparticletree.js"; +import { mcParticleTree, preFilterMCTree } from "./mcparticletree.js"; import { mcRecoAssociation, preFilterMCReco } from "./mcrecoassociation.js"; -import { - recoParticleTree, - recoParticleTreeScroll, -} from "./recoparticletree.js"; +import { recoParticleTree, preFilterRecoTree } from "./recoparticletree.js"; import { setupMCParticleFilter } from "../filter/mcparticle.js"; +import { trackTree, preFilterTrackTree } from "./tracktree.js"; +import { clusterTree, preFilterClusterTree } from "./clustertree.js"; +import { scrollTopCenter, scrollTopLeft } from "./scrolls.js"; +import { preFilterMCTrack, mcTrackAssociation } from "./mctrackassociation.js"; +import { + preFilterMCCluster, + mcClusterAssociation, +} from "./mcclusterassociation.js"; export const views = { "Monte Carlo Particle Tree": { filters: setupMCParticleFilter, viewFunction: mcParticleTree, - scrollFunction: mcParticleTreeScroll, - preFilterFunction: (currentObjects, viewObjects) => { - viewObjects.datatypes = {}; - viewObjects.associations = {}; - viewObjects.datatypes["edm4hep::MCParticle"] = - currentObjects.datatypes["edm4hep::MCParticle"]; - }, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterMCTree, }, "Reconstructed Particle Tree": { filters: () => {}, viewFunction: recoParticleTree, - scrollFunction: recoParticleTreeScroll, - preFilterFunction: (currentObjects, viewObjects) => { - viewObjects.datatypes = {}; - viewObjects.associations = {}; - viewObjects.datatypes["edm4hep::ReconstructedParticle"] = - currentObjects.datatypes["edm4hep::ReconstructedParticle"]; - }, + scrollFunction: scrollTopLeft, + preFilterFunction: preFilterRecoTree, + }, + "Track Tree": { + filters: () => {}, + viewFunction: trackTree, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterTrackTree, + }, + "Cluster Tree": { + filters: () => {}, + viewFunction: clusterTree, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterClusterTree, + }, + "Reconstructed Particle-Cluster": { + filters: () => {}, + viewFunction: () => {}, + scrollFunction: () => {}, + preFilterFunction: () => {}, + }, + "Reconstructed Particle-Track": { + filters: () => {}, + viewFunction: () => {}, + scrollFunction: () => {}, + preFilterFunction: () => {}, }, "Monte Carlo-Reconstructed Particle": { filters: () => {}, viewFunction: mcRecoAssociation, - scrollFunction: () => { - return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; - }, + scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCReco, }, + "Monte Carlo Particle-Track": { + filters: () => {}, + viewFunction: mcTrackAssociation, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterMCTrack, + }, + "Monte Carlo Particle-Cluster": { + filters: () => {}, + viewFunction: mcClusterAssociation, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterMCCluster, + }, }; diff --git a/model/index.js b/model/index.js index cfd58d44..5db3966f 100644 --- a/model/index.js +++ b/model/index.js @@ -18,6 +18,8 @@ const configTypes = new Set([ "edm4hep::ReconstructedParticle", "edm4hep::Track", "edm4hep::MCRecoParticleAssociation", + "edm4hep::MCRecoTrackParticleAssociation", + "edm4hep::MCRecoClusterParticleAssociation", ]); const selectedTypes = Object.entries(datatypes).filter(([key, _]) => diff --git a/output/datatypes.js b/output/datatypes.js index bbd0c3e0..31826a96 100644 --- a/output/datatypes.js +++ b/output/datatypes.js @@ -247,5 +247,39 @@ export const datatypes = { "name": "sim" } ] + }, + "edm4hep::MCRecoClusterParticleAssociation": { + "members": [ + { + "name": "weight" + } + ], + "oneToOneRelations": [ + { + "type": "edm4hep::Cluster", + "name": "rec" + }, + { + "type": "edm4hep::MCParticle", + "name": "sim" + } + ] + }, + "edm4hep::MCRecoTrackParticleAssociation": { + "members": [ + { + "name": "weight" + } + ], + "oneToOneRelations": [ + { + "type": "edm4hep::Track", + "name": "rec" + }, + { + "type": "edm4hep::MCParticle", + "name": "sim" + } + ] } } \ No newline at end of file