From bea1ab58be8d90e07b8021261fc0f309f7095267 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sun, 23 Jun 2024 21:51:10 -0500 Subject: [PATCH 01/21] feat: fix loading and prepare objects to be placed on canvas by algorithm --- js/draw.js | 38 ++--- js/events.js | 19 +-- js/graph/fruchrein.js | 4 + js/main.js | 16 ++- js/menu/event-number.js | 70 +++++----- js/menu/filter/reconnect.js | 2 +- js/place-objects.js | 65 +++++++++ js/types/dynamic.js | 56 -------- js/types/edmobject.js | 8 -- js/types/links.js | 9 ++ js/types/load.js | 270 +++++++++++++++++++++++++++--------- js/types/objects.js | 90 ++++++------ model/index.js | 11 +- output/datatypes.js | 29 ++++ 14 files changed, 442 insertions(+), 245 deletions(-) create mode 100644 js/graph/fruchrein.js create mode 100644 js/place-objects.js delete mode 100644 js/types/dynamic.js delete mode 100644 js/types/edmobject.js diff --git a/js/draw.js b/js/draw.js index 65159e75..7cf6ba9d 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,21 +1,33 @@ import { canvas, ctx } from "./main.js"; -export function drawAll(ctx, loadedObjects) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - for (const elements of Object.values(loadedObjects)) { +function draw(objects) { + for (const elements of Object.values(objects.datatypes ?? {})) { const { collection, oneToMany, oneToOne } = elements; for (const links of Object.values(oneToMany)) { - for (const link of links) link.draw(ctx); + for (const link of links) { + link.draw(ctx); + } } - for (const link of Object.values(oneToOne)) link.draw(ctx); + for (const links of Object.values(oneToOne)) { + for (const link of links) { + link.draw(ctx); + } + } - for (const object of collection) object.draw(ctx); + for (const object of collection) { + object.draw(ctx); + } } } +export function drawAll(ctx, loadedObjects) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + draw(loadedObjects); +} + export function drawVisible(visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); ctx.clearRect( @@ -25,15 +37,5 @@ export function drawVisible(visibleObjects) { window.innerHeight ); - for (const elements of Object.values(visibleObjects)) { - const { collection, oneToMany, oneToOne } = elements; - - for (const links of Object.values(oneToMany)) { - for (const link of links) link.draw(ctx); - } - - for (const link of Object.values(oneToOne)) link.draw(ctx); - - for (const object of collection) object.draw(ctx); - } + draw(visibleObjects); } diff --git a/js/events.js b/js/events.js index 660ec57b..b06e90b3 100644 --- a/js/events.js +++ b/js/events.js @@ -10,7 +10,7 @@ const mouseDown = function (event, visibleObjects, dragTools) { dragTools.prevMouseX = mouseX; dragTools.prevMouseY = mouseY; - for (const { collection } of Object.values(visibleObjects)) { + for (const { collection } of Object.values(visibleObjects.datatypes)) { for (const object of collection) { if (object.isHere(mouseX, mouseY)) { dragTools.draggedObject = object; @@ -71,10 +71,13 @@ const mouseMove = function (event, visibleObjects, dragTools) { const getVisible = function (loadedObjects, visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); - for (const [objectType, elements] of Object.entries(loadedObjects)) { + visibleObjects.datatypes = {}; + for (const [objectType, elements] of Object.entries( + loadedObjects.datatypes ?? {} + )) { const { collection, oneToMany, oneToOne } = elements; - visibleObjects[objectType] = { + visibleObjects.datatypes[objectType] = { collection: [], oneToMany: {}, oneToOne: {}, @@ -89,12 +92,12 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].collection.push(object); + visibleObjects.datatypes[objectType].collection.push(object); } } for (const [name, links] of Object.entries(oneToMany)) { - visibleObjects[objectType].oneToMany[name] = []; + visibleObjects.datatypes[objectType].oneToMany[name] = []; for (const link of links) { if ( @@ -105,13 +108,13 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].oneToMany[name].push(link); + visibleObjects.datatypes[objectType].oneToMany[name].push(link); } } } for (const [name, links] of Object.entries(oneToOne)) { - visibleObjects[objectType].oneToOne[name] = null; + visibleObjects.datatypes[objectType].oneToOne[name] = null; for (const link of links) { if ( @@ -122,7 +125,7 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].oneToOne[name] = link; + visibleObjects.datatypes[objectType].oneToOne[name] = link; } } } diff --git a/js/graph/fruchrein.js b/js/graph/fruchrein.js new file mode 100644 index 00000000..8a7334ee --- /dev/null +++ b/js/graph/fruchrein.js @@ -0,0 +1,4 @@ +const nodeHeight = 240; +const nodeWidth = 120; + +export function fruchtermanReingold(nodes, edges) {} diff --git a/js/main.js b/js/main.js index f76ce42e..f820903f 100644 --- a/js/main.js +++ b/js/main.js @@ -3,7 +3,6 @@ import { PdgToggle } from "./menu/show-pdg.js"; import { drawAll } from "./draw.js"; import { getWidthFilterContent } from "./menu/filter/filter.js"; import { mouseDown, mouseUp, mouseOut, mouseMove, onScroll } from "./events.js"; -import { showEventSwitcher, loadSelectedEvent } from "./menu/event-number.js"; import { renderEvent } from "./menu/event-number.js"; const canvas = document.getElementById("canvas"); @@ -28,7 +27,11 @@ const currentObjects = {}; const visibleObjects = {}; const selectedObjectTypes = { - types: ["edm4hep::MCParticle"], + types: [ + "edm4hep::MCParticle", + "edm4hep::ReconstructedParticle", + "edm4hep::MCRecoParticleAssociation", + ], }; canvas.onmousedown = (event) => { @@ -53,6 +56,11 @@ function hideInputModal() { modal.style.display = "none"; } +function showEventSwitcher() { + const eventSwitcher = document.getElementById("event-switcher"); + eventSwitcher.style.display = "flex"; +} + document.getElementById("input-file").addEventListener("change", (event) => { for (const file of event.target.files) { if (!file.name.endsWith("edm4hep.json")) { @@ -110,8 +118,8 @@ document const eventNum = document.getElementById("event-number").value; hideInputModal(); - showEventSwitcher(eventNum); - loadSelectedEvent(); + showEventSwitcher(); + renderEvent(eventNum); const width = getWidthFilterContent(); filter.style.width = width; diff --git a/js/menu/event-number.js b/js/menu/event-number.js index 273a817e..d4fa8b87 100644 --- a/js/menu/event-number.js +++ b/js/menu/event-number.js @@ -18,9 +18,9 @@ import { import { drawAll } from "../draw.js"; import { objectTypes } from "../types/objects.js"; import { jsonData, selectedObjectTypes } from "../main.js"; +import { placeObjects } from "../place-objects.js"; const filters = document.getElementById("filters"); -const eventSwitcher = document.getElementById("event-switcher"); const eventNumber = document.getElementById("selected-event"); const previousEvent = document.getElementById("previous-event"); const nextEvent = document.getElementById("next-event"); @@ -30,49 +30,24 @@ let currentEvent; const scrollLocation = {}; -function updateEventNumber(newEventNumber) { +const layoutObjects = []; + +function updateEventNumber() { if (eventNumber.firstChild) { eventNumber.removeChild(eventNumber.firstChild); } - eventNumber.appendChild(document.createTextNode(`Event: ${newEventNumber}`)); -} - -function start(currentObjects, visibleObjects) { - for (const [key, value] of Object.entries(currentObjects)) { - const classType = objectTypes[key]; - const collection = value.collection; - classType.setup(collection, canvas); - } - - drawAll(ctx, currentObjects); - - getVisible(currentObjects, visibleObjects); + eventNumber.appendChild(document.createTextNode(`Event: ${currentEvent}`)); } -export function renderEvent(eventNumber) { - const data = jsonData.data[`Event ${eventNumber}`]; - +function saveScrollLocation() { + if (scrollLocation[currentEvent] === undefined) return; scrollLocation[currentEvent] = { x: window.scrollX, y: window.scrollY, }; - - if (data === undefined) { - return; - } else { - currentEvent = eventNumber; - loadSelectedEvent(jsonData, selectedObjectTypes.types, eventNumber); - updateEventNumber(eventNumber); - } -} - -export function showEventSwitcher(initialValue) { - eventSwitcher.style.display = "flex"; - updateEventNumber(initialValue); - currentEvent = initialValue; } -export function loadSelectedEvent() { +function loadSelectedEvent() { const objects = loadObjects( jsonData.data, currentEvent, @@ -82,7 +57,7 @@ export function loadSelectedEvent() { copyObject(objects, loadedObjects); copyObject(objects, currentObjects); - const length = Object.values(loadedObjects) + const length = Object.values(loadedObjects.datatypes) .map((obj) => obj.collection.length) .reduce((a, b) => a + b, 0); @@ -91,7 +66,21 @@ export function loadSelectedEvent() { return; } - start(currentObjects, visibleObjects); + for (const [key, value] of Object.entries(currentObjects.datatypes)) { + const classType = objectTypes[key]; + const collection = value.collection; + classType.setup(collection, canvas); + } + + // Prepare objects for drawing + // if (!layoutObjects[currentEvent]) { + placeObjects(currentObjects); + // layoutObjects[currentEvent] = true; + // } + + drawAll(ctx, currentObjects); + getVisible(currentObjects, visibleObjects); + if (scrollLocation[currentEvent] === undefined) { scrollLocation[currentEvent] = { x: (canvas.width - window.innerWidth) / 2, @@ -101,11 +90,11 @@ export function loadSelectedEvent() { window.scroll(scrollLocation[currentEvent].x, scrollLocation[currentEvent].y); + // menu/filtering stuff for (const tool of manipulationTools) { tool.style.display = "flex"; } - - const mcObjects = loadedObjects["edm4hep::MCParticle"].collection; + const mcObjects = loadedObjects.datatypes["edm4hep::MCParticle"].collection; genStatus.reset(); mcObjects.forEach((mcObject) => { genStatus.add(mcObject.generatorStatus); @@ -117,6 +106,13 @@ export function loadSelectedEvent() { renderGenSim(bits, genStatus); } +export function renderEvent(eventNumber) { + saveScrollLocation(); + currentEvent = eventNumber; + loadSelectedEvent(); + updateEventNumber(); +} + previousEvent.addEventListener("click", () => { const newEventNum = `${parseInt(currentEvent) - 1}`; renderEvent(newEventNum); diff --git a/js/menu/filter/reconnect.js b/js/menu/filter/reconnect.js index e33e4eee..e4ceb28f 100644 --- a/js/menu/filter/reconnect.js +++ b/js/menu/filter/reconnect.js @@ -6,7 +6,7 @@ export function reconnect(criteriaFunction, loadedObjects) { emptyCopyObject(loadedObjects, filteredObjects); - for (const [key, value] of Object.entries(loadedObjects)) { + for (const [key, value] of Object.entries(loadedObjects.datatypes)) { const filterFunction = objectTypes[key].filter; filterFunction(value, filteredObjects, criteriaFunction); diff --git a/js/place-objects.js b/js/place-objects.js new file mode 100644 index 00000000..0b64ce05 --- /dev/null +++ b/js/place-objects.js @@ -0,0 +1,65 @@ +import { fruchtermanReingold } from "./graph/fruchrein.js"; + +function objectToNode(object) { + const edges = []; + + const oneToManyRelations = object.oneToManyRelations ?? {}; + const oneToOneRelations = object.oneToOneRelations ?? {}; + const associations = object.associations ?? {}; + + for (const link of Object.values(oneToOneRelations)) { + edges.push(link); + } + + for (const link of Object.values(associations)) { + edges.push(link); + } + + for (const links of Object.values(oneToManyRelations)) { + for (const link of links) { + edges.push(link); + } + } + + return { + x: object.x, + y: object.y, + width: object.width, + height: object.height, + edges, + }; +} + +export function placeObjects(objects) { + const nodes = []; + const edges = []; + + const datatypes = objects.datatypes; + const associations = objects.associations; + + for (const { collection, oneToOne, oneToMany } of Object.values(datatypes)) { + for (const object of collection) { + nodes.push(objectToNode(object)); + } + for (const links of Object.values(oneToOne)) { + for (const link of links) { + edges.push(link); + } + } + for (const links of Object.values(oneToMany)) { + for (const link of links) { + edges.push(link); + } + } + } + + for (const collection of Object.values(associations)) { + for (const association of collection) { + edges.push(association); + } + } + + console.log(nodes, edges); + + fruchtermanReingold(nodes, edges); +} diff --git a/js/types/dynamic.js b/js/types/dynamic.js deleted file mode 100644 index fa250270..00000000 --- a/js/types/dynamic.js +++ /dev/null @@ -1,56 +0,0 @@ -import { linkTypes } from "./links.js"; - -export function loadMembers(object, data, membersToLoad) { - for (const member of membersToLoad) { - const name = member.name; - if (data[name] === undefined) continue; // load up to date data - object[name] = data[name]; - } -} - -export function loadOneToOneRelations( - object, - data, - relationsToLoad = [], - oneToOne, - objects -) { - object.oneToOneRelations = {}; - for (const relation of relationsToLoad) { - const name = relation.name; - const relationData = data[name]; - if (relationData === undefined) continue; - - const toObject = objects[relationData.index]; - const linkType = linkTypes[name]; - const link = new linkType(object, toObject); - - oneToOne[name].push(link); - object.oneToOneRelations[name] = link; - } -} - -export function loadOneToManyRelations( - object, - data, - relationsToLoad = [], - oneToMany, - objects -) { - object.oneToManyRelations = {}; - for (const relation of relationsToLoad) { - const name = relation.name; - const relationData = data[name]; - - if (relationData === undefined) continue; - object.oneToManyRelations[name] = []; - - for (const relationElement of relationData) { - const toObject = objects[relationElement.index]; - const linkType = linkTypes[name]; - const link = new linkType(object, toObject); - oneToMany[name].push(link); - object.oneToManyRelations[name].push(link); - } - } -} diff --git a/js/types/edmobject.js b/js/types/edmobject.js deleted file mode 100644 index 52332c34..00000000 --- a/js/types/edmobject.js +++ /dev/null @@ -1,8 +0,0 @@ -export class EDMObject { - constructor(id) { - this.id = id; - } - - draw() {} - // more methods common to all particles -} diff --git a/js/types/links.js b/js/types/links.js index 5f2914d1..3a550525 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -128,10 +128,19 @@ export class DaughterLink extends Link { } } +export class MCRecoParticleAssociation extends Link { + constructor(from, to, weight) { + super(from, to); + this.weight = weight; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, "trackerHits": Link, "startVertex": Link, "particles": Link, + "clusters": Link, + "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, }; diff --git a/js/types/load.js b/js/types/load.js index 1623a67d..9e809811 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -1,88 +1,228 @@ import { objectTypes } from "./objects.js"; import { datatypes } from "../../output/datatypes.js"; -import { - loadMembers, - loadOneToOneRelations, - loadOneToManyRelations, -} from "./dynamic.js"; -import { generateRandomColor, colors } from "./links.js"; - -export function loadObjectType(collection, datatype, type) { - const objects = []; - let oneToOne = {}; - if (datatype.oneToOneRelations) - datatype.oneToOneRelations.forEach((relation) => { - oneToOne[relation.name] = []; - if (colors[relation.name] === undefined) { - colors[relation.name] = generateRandomColor(); - } - }); - - let oneToMany = {}; - if (datatype.oneToManyRelations) - datatype.oneToManyRelations.forEach((relation) => { - oneToMany[relation.name] = []; - if (colors[relation.name] === undefined) { - colors[relation.name] = generateRandomColor(); - } - }); +import { linkTypes } from "./links.js"; - for (const [index, particle] of collection.entries()) { - const newObject = new type(index); +// import json from "../../input/p8_ee_ZH_ecm240_edm4hep.edm4hep.json" assert { type: "json" }; - loadMembers(newObject, particle, datatype.members); +function loadMembers(object, data, membersToLoad) { + for (const member of membersToLoad) { + const name = member.name; + if (data[name] === undefined) continue; // load up to date data + object[name] = data[name]; + } +} - objects.push(newObject); +function loadEmptyRelations(object, relations) { + const oneToOneRelations = relations.oneToOneRelations ?? []; + if (oneToOneRelations) object.oneToOneRelations = {}; + // for (const { name } of oneToOneRelations) { + // object.oneToOneRelations[name] = null; + // } + + const oneToManyRelations = relations.oneToManyRelations ?? []; + if (oneToManyRelations) object.oneToManyRelations = {}; + for (const { name } of oneToManyRelations) { + object.oneToManyRelations[name] = []; } +} + +export function loadPlainObject(collection, datatype, collectionId) { + const objects = []; for (const [index, particle] of collection.entries()) { - const newObject = objects[index]; - - loadOneToOneRelations( - newObject, - particle, - datatype.oneToOneRelations, - oneToOne, - objects - ); - - loadOneToManyRelations( - newObject, - particle, - datatype.oneToManyRelations, - oneToMany, - objects - ); + const newObject = new objectTypes[datatype](); + newObject.index = index; + newObject.collectionId = collectionId; + + loadMembers(newObject, particle, datatypes[datatype].members); + loadEmptyRelations(newObject, datatypes[datatype]); + + objects.push(newObject); } - return [objects, oneToOne, oneToMany]; + return objects; } export function loadObjects(jsonData, event, objectsToLoad) { const eventData = jsonData["Event " + event]; - const objects = {}; + const datatypesToLoad = objectsToLoad.filter( + (object) => !object.includes("Association") + ); + const associations = objectsToLoad.filter((object) => + object.includes("Association") + ); + + const objects = { + "datatypes": {}, + "associations": {}, + }; + + datatypesToLoad.forEach((datatype) => { + objects.datatypes[datatype] = { + collection: [], + oneToMany: {}, + oneToOne: {}, + }; + }); + + associations.forEach((association) => { + objects.associations[association] = []; + }); + + for (const datatype of datatypesToLoad) { + Object.values(eventData).forEach((element) => { + const collectionName = `${datatype}Collection`; + if (element.collType === collectionName) { + const collection = element.collection; + const collectionId = element.collID; + const objectCollection = loadPlainObject( + collection, + datatype, + collectionId + ); + objects.datatypes[datatype].collection.push(...objectCollection); + } + }); + } - for (const type of objectsToLoad) { - let collectionType = Object.values(eventData).filter( - (element) => element.collType === `${type}Collection` - ); + for (const datatype of datatypesToLoad) { + const oneToOneRelations = datatypes?.[datatype]?.oneToOneRelations ?? []; + oneToOneRelations.forEach((relation) => { + objects.datatypes[datatype].oneToOne[relation.name] = []; + }); - collectionType = collectionType.map((coll) => coll.collection); - collectionType = collectionType.flat(); + const oneToManyRelations = datatypes?.[datatype]?.oneToManyRelations ?? []; + oneToManyRelations.forEach((relation) => { + objects.datatypes[datatype].oneToMany[relation.name] = []; + }); - const [loadedCollection, oneToOne, oneToMany] = loadObjectType( - collectionType, - datatypes[type], - objectTypes[type] - ); + Object.values(eventData).forEach((element) => { + const collectionName = `${datatype}Collection`; + if (element.collType === collectionName) { + const fromCollection = objects.datatypes[datatype].collection.filter( + (object) => object.collectionId === element.collID + ); + + // load One To One Relations + for (const { type, name } of oneToOneRelations) { + if (objects.datatypes?.[type] === undefined) continue; + const oneToOneRelationData = element.collection.map( + (object) => object[name] + ); + const toCollectionID = + oneToOneRelationData.find( + (relation) => relation.collectionID !== undefined + ).collectionID ?? NaN; + + const toCollection = objects.datatypes[type].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + if (toCollection) { + for (const [index, relation] of oneToOneRelationData.entries()) { + if (relation.index < 0) continue; + const fromObject = fromCollection[index]; + const toObject = toCollection[relation.index]; + + const linkType = linkTypes[name]; + const link = new linkType(fromObject, toObject); + fromObject.oneToOneRelations[name] = link; + objects.datatypes[datatype].oneToOne[name].push(link); + } + } + } + + // load One To Many Relations + for (const { type, name } of oneToManyRelations) { + if (objects.datatypes?.[type] === undefined) continue; + const oneToManyRelationData = element.collection.map( + (object) => object[name] + ); + + const toCollectionID = + oneToManyRelationData.find( + (relation) => relation?.[0]?.collectionID !== undefined + )?.[0]?.collectionID ?? NaN; + const toCollection = objects.datatypes[type].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + if (toCollection) { + for (const [index, relation] of oneToManyRelationData.entries()) { + if (relation.length === 0) continue; + const fromObject = fromCollection[index]; + for (const relationElement of relation) { + if (relationElement.index < 0) continue; + const toObject = toCollection[relationElement.index]; + + const linkType = linkTypes[name]; + const link = new linkType(fromObject, toObject); + fromObject.oneToManyRelations[name].push(link); + objects.datatypes[datatype].oneToMany[name].push(link); + } + } + } + } + } + }); + } - objects[type] = { - collection: loadedCollection, - oneToMany: oneToMany, - oneToOne: oneToOne, - }; + // Currently, all associations are one-to-one + for (const association of associations) { + Object.values(eventData).forEach((element) => { + const collectionName = `${association}Collection`; + if (element.collType === collectionName) { + const collection = element.collection; + if (collection.length === 0) return; + + const { type: fromType, name: fromName } = + datatypes[association].oneToOneRelations[0]; + const { type: toType, name: toName } = + datatypes[association].oneToOneRelations[1]; + + const fromCollectionID = collection.find( + (relation) => relation[fromName].collectionID !== undefined + )[fromName].collectionID; + const toCollectionID = collection.find( + (relation) => relation[toName].collectionID !== undefined + )[toName].collectionID; + + const fromCollection = objects.datatypes[fromType].collection.filter( + (object) => object.collectionId === fromCollectionID + ); + const toCollection = objects.datatypes[toType].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + for (const associationElement of collection) { + const fromObject = fromCollection[associationElement[fromName].index]; + const toObject = toCollection[associationElement[toName].index]; + + const linkType = linkTypes[association]; + const link = new linkType( + fromObject, + toObject, + associationElement.weight + ); + objects.associations[association].push(link); + fromObject.associations = {}; + fromObject.associations[association] = link; + toObject.associations = {}; + toObject.associations[association] = link; + } + } + }); } 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 d0fe160f..55c8a8ee 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -1,50 +1,76 @@ -import { EDMObject } from "./edmobject.js"; import { drawTex, drawRoundedRect } from "../graphic-primitives.js"; import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; +class EDMObject { + constructor() { + this.x = 0; + this.y = 0; + this.width = 120; + this.height = 240; + this.lineColor = "black"; + this.lineWidth = 2; + this.color = "white"; + } + + draw(ctx) {} + + isHere(mouseX, mouseY) { + return ( + mouseX > this.x && + mouseX < this.x + this.width && + mouseY > this.y && + mouseY < this.y + this.height + ); + } + + isVisible(x, y, width, height) { + return ( + x + width > this.x && + x < this.x + this.width && + y + height > this.y && + y < this.y + this.height + ); + } + // more methods common to all particles +} + export class Cluster extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class ParticleID extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class ReconstructedParticle extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } + + static setup() {} } export class Vertex extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class Track extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class MCParticle extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); - // Appearance - this.x = 0; - this.y = 0; - this.width = 120; - this.height = 240; - this.lineColor = "black"; - this.lineWidth = 2; - this.color = "white"; this.row = -1; this.texImg = null; @@ -63,8 +89,6 @@ export class MCParticle extends EDMObject { } draw(ctx) { - // drawCross(ctx, this.x, this.y); - const boxCenterX = this.x + this.width / 2; drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); @@ -91,7 +115,7 @@ export class MCParticle extends EDMObject { const topY = this.y + 20; const topLines = []; - topLines.push("ID: " + this.id); + topLines.push("ID: " + this.index); topLines.push("Gen. stat.: " + this.generatorStatus); topLines.push("Sim. stat.: " + this.simulatorStatus); @@ -138,24 +162,6 @@ export class MCParticle extends EDMObject { ctx.restore(); } - isHere(mouseX, mouseY) { - return ( - mouseX > this.x && - mouseX < this.x + this.width && - mouseY > this.y && - mouseY < this.y + this.height - ); - } - - isVisible(x, y, width, height) { - return ( - x + width > this.x && - x < this.x + this.width && - y + height > this.y && - y < this.y + this.height - ); - } - static setup(mcCollection, canvas) { for (const mcParticle of mcCollection) { const parentLength = mcParticle.oneToManyRelations["parents"].length; diff --git a/model/index.js b/model/index.js index 1670a059..cfd58d44 100644 --- a/model/index.js +++ b/model/index.js @@ -17,17 +17,15 @@ const configTypes = new Set([ "edm4hep::Vertex", "edm4hep::ReconstructedParticle", "edm4hep::Track", + "edm4hep::MCRecoParticleAssociation", ]); const selectedTypes = Object.entries(datatypes).filter(([key, _]) => configTypes.has(key) ); -const componentsDefinition = {}; const datatypesDefinition = {}; -class Component {} - class DataTypeMember { constructor(name, unit = null) { this.name = name; @@ -36,7 +34,8 @@ class DataTypeMember { } class Relation { - constructor(name) { + constructor(type, name) { + this.type = type; this.name = name; } } @@ -63,9 +62,9 @@ const parseDatatypesMembers = (members) => { const parseRelation = (relations) => { return relations.map((relation) => { - const [_, name] = parseString(relation); + const [type, name] = parseString(relation); - return new Relation(name); + return new Relation(type, name); }); }; diff --git a/output/datatypes.js b/output/datatypes.js index 942da49d..bbd0c3e0 100644 --- a/output/datatypes.js +++ b/output/datatypes.js @@ -46,9 +46,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::MCParticle", "name": "parents" }, { + "type": "edm4hep::MCParticle", "name": "daughters" } ] @@ -70,6 +72,7 @@ export const datatypes = { ], "oneToOneRelations": [ { + "type": "edm4hep::ReconstructedParticle", "name": "particle" } ] @@ -107,9 +110,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::Cluster", "name": "clusters" }, { + "type": "edm4hep::CalorimeterHit", "name": "hits" } ] @@ -137,9 +142,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::TrackerHit", "name": "trackerHits" }, { + "type": "edm4hep::Track", "name": "tracks" } ] @@ -167,6 +174,7 @@ export const datatypes = { ], "oneToOneRelations": [ { + "type": "edm4hep::ReconstructedParticle", "name": "associatedParticle" } ] @@ -204,19 +212,40 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::Cluster", "name": "clusters" }, { + "type": "edm4hep::Track", "name": "tracks" }, { + "type": "edm4hep::ReconstructedParticle", "name": "particles" } ], "oneToOneRelations": [ { + "type": "edm4hep::Vertex", "name": "startVertex" } ] + }, + "edm4hep::MCRecoParticleAssociation": { + "members": [ + { + "name": "weight" + } + ], + "oneToOneRelations": [ + { + "type": "edm4hep::ReconstructedParticle", + "name": "rec" + }, + { + "type": "edm4hep::MCParticle", + "name": "sim" + } + ] } } \ No newline at end of file From 771429103932842ff89f6a21f3d322a1ea180a51 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sun, 23 Jun 2024 21:51:10 -0500 Subject: [PATCH 02/21] feat: fix loading and prepare objects to be placed on canvas by algorithm --- js/draw.js | 38 ++--- js/events.js | 19 +-- js/graph/fruchrein.js | 4 + js/main.js | 16 ++- js/menu/event-number.js | 70 +++++----- js/menu/filter/reconnect.js | 2 +- js/place-objects.js | 65 +++++++++ js/types/dynamic.js | 56 -------- js/types/edmobject.js | 8 -- js/types/links.js | 9 ++ js/types/load.js | 270 +++++++++++++++++++++++++++--------- js/types/objects.js | 90 ++++++------ model/index.js | 11 +- output/datatypes.js | 29 ++++ 14 files changed, 442 insertions(+), 245 deletions(-) create mode 100644 js/graph/fruchrein.js create mode 100644 js/place-objects.js delete mode 100644 js/types/dynamic.js delete mode 100644 js/types/edmobject.js diff --git a/js/draw.js b/js/draw.js index 65159e75..7cf6ba9d 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,21 +1,33 @@ import { canvas, ctx } from "./main.js"; -export function drawAll(ctx, loadedObjects) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - for (const elements of Object.values(loadedObjects)) { +function draw(objects) { + for (const elements of Object.values(objects.datatypes ?? {})) { const { collection, oneToMany, oneToOne } = elements; for (const links of Object.values(oneToMany)) { - for (const link of links) link.draw(ctx); + for (const link of links) { + link.draw(ctx); + } } - for (const link of Object.values(oneToOne)) link.draw(ctx); + for (const links of Object.values(oneToOne)) { + for (const link of links) { + link.draw(ctx); + } + } - for (const object of collection) object.draw(ctx); + for (const object of collection) { + object.draw(ctx); + } } } +export function drawAll(ctx, loadedObjects) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + draw(loadedObjects); +} + export function drawVisible(visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); ctx.clearRect( @@ -25,15 +37,5 @@ export function drawVisible(visibleObjects) { window.innerHeight ); - for (const elements of Object.values(visibleObjects)) { - const { collection, oneToMany, oneToOne } = elements; - - for (const links of Object.values(oneToMany)) { - for (const link of links) link.draw(ctx); - } - - for (const link of Object.values(oneToOne)) link.draw(ctx); - - for (const object of collection) object.draw(ctx); - } + draw(visibleObjects); } diff --git a/js/events.js b/js/events.js index 660ec57b..b06e90b3 100644 --- a/js/events.js +++ b/js/events.js @@ -10,7 +10,7 @@ const mouseDown = function (event, visibleObjects, dragTools) { dragTools.prevMouseX = mouseX; dragTools.prevMouseY = mouseY; - for (const { collection } of Object.values(visibleObjects)) { + for (const { collection } of Object.values(visibleObjects.datatypes)) { for (const object of collection) { if (object.isHere(mouseX, mouseY)) { dragTools.draggedObject = object; @@ -71,10 +71,13 @@ const mouseMove = function (event, visibleObjects, dragTools) { const getVisible = function (loadedObjects, visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); - for (const [objectType, elements] of Object.entries(loadedObjects)) { + visibleObjects.datatypes = {}; + for (const [objectType, elements] of Object.entries( + loadedObjects.datatypes ?? {} + )) { const { collection, oneToMany, oneToOne } = elements; - visibleObjects[objectType] = { + visibleObjects.datatypes[objectType] = { collection: [], oneToMany: {}, oneToOne: {}, @@ -89,12 +92,12 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].collection.push(object); + visibleObjects.datatypes[objectType].collection.push(object); } } for (const [name, links] of Object.entries(oneToMany)) { - visibleObjects[objectType].oneToMany[name] = []; + visibleObjects.datatypes[objectType].oneToMany[name] = []; for (const link of links) { if ( @@ -105,13 +108,13 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].oneToMany[name].push(link); + visibleObjects.datatypes[objectType].oneToMany[name].push(link); } } } for (const [name, links] of Object.entries(oneToOne)) { - visibleObjects[objectType].oneToOne[name] = null; + visibleObjects.datatypes[objectType].oneToOne[name] = null; for (const link of links) { if ( @@ -122,7 +125,7 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].oneToOne[name] = link; + visibleObjects.datatypes[objectType].oneToOne[name] = link; } } } diff --git a/js/graph/fruchrein.js b/js/graph/fruchrein.js new file mode 100644 index 00000000..8a7334ee --- /dev/null +++ b/js/graph/fruchrein.js @@ -0,0 +1,4 @@ +const nodeHeight = 240; +const nodeWidth = 120; + +export function fruchtermanReingold(nodes, edges) {} diff --git a/js/main.js b/js/main.js index f76ce42e..f820903f 100644 --- a/js/main.js +++ b/js/main.js @@ -3,7 +3,6 @@ import { PdgToggle } from "./menu/show-pdg.js"; import { drawAll } from "./draw.js"; import { getWidthFilterContent } from "./menu/filter/filter.js"; import { mouseDown, mouseUp, mouseOut, mouseMove, onScroll } from "./events.js"; -import { showEventSwitcher, loadSelectedEvent } from "./menu/event-number.js"; import { renderEvent } from "./menu/event-number.js"; const canvas = document.getElementById("canvas"); @@ -28,7 +27,11 @@ const currentObjects = {}; const visibleObjects = {}; const selectedObjectTypes = { - types: ["edm4hep::MCParticle"], + types: [ + "edm4hep::MCParticle", + "edm4hep::ReconstructedParticle", + "edm4hep::MCRecoParticleAssociation", + ], }; canvas.onmousedown = (event) => { @@ -53,6 +56,11 @@ function hideInputModal() { modal.style.display = "none"; } +function showEventSwitcher() { + const eventSwitcher = document.getElementById("event-switcher"); + eventSwitcher.style.display = "flex"; +} + document.getElementById("input-file").addEventListener("change", (event) => { for (const file of event.target.files) { if (!file.name.endsWith("edm4hep.json")) { @@ -110,8 +118,8 @@ document const eventNum = document.getElementById("event-number").value; hideInputModal(); - showEventSwitcher(eventNum); - loadSelectedEvent(); + showEventSwitcher(); + renderEvent(eventNum); const width = getWidthFilterContent(); filter.style.width = width; diff --git a/js/menu/event-number.js b/js/menu/event-number.js index 273a817e..d4fa8b87 100644 --- a/js/menu/event-number.js +++ b/js/menu/event-number.js @@ -18,9 +18,9 @@ import { import { drawAll } from "../draw.js"; import { objectTypes } from "../types/objects.js"; import { jsonData, selectedObjectTypes } from "../main.js"; +import { placeObjects } from "../place-objects.js"; const filters = document.getElementById("filters"); -const eventSwitcher = document.getElementById("event-switcher"); const eventNumber = document.getElementById("selected-event"); const previousEvent = document.getElementById("previous-event"); const nextEvent = document.getElementById("next-event"); @@ -30,49 +30,24 @@ let currentEvent; const scrollLocation = {}; -function updateEventNumber(newEventNumber) { +const layoutObjects = []; + +function updateEventNumber() { if (eventNumber.firstChild) { eventNumber.removeChild(eventNumber.firstChild); } - eventNumber.appendChild(document.createTextNode(`Event: ${newEventNumber}`)); -} - -function start(currentObjects, visibleObjects) { - for (const [key, value] of Object.entries(currentObjects)) { - const classType = objectTypes[key]; - const collection = value.collection; - classType.setup(collection, canvas); - } - - drawAll(ctx, currentObjects); - - getVisible(currentObjects, visibleObjects); + eventNumber.appendChild(document.createTextNode(`Event: ${currentEvent}`)); } -export function renderEvent(eventNumber) { - const data = jsonData.data[`Event ${eventNumber}`]; - +function saveScrollLocation() { + if (scrollLocation[currentEvent] === undefined) return; scrollLocation[currentEvent] = { x: window.scrollX, y: window.scrollY, }; - - if (data === undefined) { - return; - } else { - currentEvent = eventNumber; - loadSelectedEvent(jsonData, selectedObjectTypes.types, eventNumber); - updateEventNumber(eventNumber); - } -} - -export function showEventSwitcher(initialValue) { - eventSwitcher.style.display = "flex"; - updateEventNumber(initialValue); - currentEvent = initialValue; } -export function loadSelectedEvent() { +function loadSelectedEvent() { const objects = loadObjects( jsonData.data, currentEvent, @@ -82,7 +57,7 @@ export function loadSelectedEvent() { copyObject(objects, loadedObjects); copyObject(objects, currentObjects); - const length = Object.values(loadedObjects) + const length = Object.values(loadedObjects.datatypes) .map((obj) => obj.collection.length) .reduce((a, b) => a + b, 0); @@ -91,7 +66,21 @@ export function loadSelectedEvent() { return; } - start(currentObjects, visibleObjects); + for (const [key, value] of Object.entries(currentObjects.datatypes)) { + const classType = objectTypes[key]; + const collection = value.collection; + classType.setup(collection, canvas); + } + + // Prepare objects for drawing + // if (!layoutObjects[currentEvent]) { + placeObjects(currentObjects); + // layoutObjects[currentEvent] = true; + // } + + drawAll(ctx, currentObjects); + getVisible(currentObjects, visibleObjects); + if (scrollLocation[currentEvent] === undefined) { scrollLocation[currentEvent] = { x: (canvas.width - window.innerWidth) / 2, @@ -101,11 +90,11 @@ export function loadSelectedEvent() { window.scroll(scrollLocation[currentEvent].x, scrollLocation[currentEvent].y); + // menu/filtering stuff for (const tool of manipulationTools) { tool.style.display = "flex"; } - - const mcObjects = loadedObjects["edm4hep::MCParticle"].collection; + const mcObjects = loadedObjects.datatypes["edm4hep::MCParticle"].collection; genStatus.reset(); mcObjects.forEach((mcObject) => { genStatus.add(mcObject.generatorStatus); @@ -117,6 +106,13 @@ export function loadSelectedEvent() { renderGenSim(bits, genStatus); } +export function renderEvent(eventNumber) { + saveScrollLocation(); + currentEvent = eventNumber; + loadSelectedEvent(); + updateEventNumber(); +} + previousEvent.addEventListener("click", () => { const newEventNum = `${parseInt(currentEvent) - 1}`; renderEvent(newEventNum); diff --git a/js/menu/filter/reconnect.js b/js/menu/filter/reconnect.js index e33e4eee..e4ceb28f 100644 --- a/js/menu/filter/reconnect.js +++ b/js/menu/filter/reconnect.js @@ -6,7 +6,7 @@ export function reconnect(criteriaFunction, loadedObjects) { emptyCopyObject(loadedObjects, filteredObjects); - for (const [key, value] of Object.entries(loadedObjects)) { + for (const [key, value] of Object.entries(loadedObjects.datatypes)) { const filterFunction = objectTypes[key].filter; filterFunction(value, filteredObjects, criteriaFunction); diff --git a/js/place-objects.js b/js/place-objects.js new file mode 100644 index 00000000..0b64ce05 --- /dev/null +++ b/js/place-objects.js @@ -0,0 +1,65 @@ +import { fruchtermanReingold } from "./graph/fruchrein.js"; + +function objectToNode(object) { + const edges = []; + + const oneToManyRelations = object.oneToManyRelations ?? {}; + const oneToOneRelations = object.oneToOneRelations ?? {}; + const associations = object.associations ?? {}; + + for (const link of Object.values(oneToOneRelations)) { + edges.push(link); + } + + for (const link of Object.values(associations)) { + edges.push(link); + } + + for (const links of Object.values(oneToManyRelations)) { + for (const link of links) { + edges.push(link); + } + } + + return { + x: object.x, + y: object.y, + width: object.width, + height: object.height, + edges, + }; +} + +export function placeObjects(objects) { + const nodes = []; + const edges = []; + + const datatypes = objects.datatypes; + const associations = objects.associations; + + for (const { collection, oneToOne, oneToMany } of Object.values(datatypes)) { + for (const object of collection) { + nodes.push(objectToNode(object)); + } + for (const links of Object.values(oneToOne)) { + for (const link of links) { + edges.push(link); + } + } + for (const links of Object.values(oneToMany)) { + for (const link of links) { + edges.push(link); + } + } + } + + for (const collection of Object.values(associations)) { + for (const association of collection) { + edges.push(association); + } + } + + console.log(nodes, edges); + + fruchtermanReingold(nodes, edges); +} diff --git a/js/types/dynamic.js b/js/types/dynamic.js deleted file mode 100644 index fa250270..00000000 --- a/js/types/dynamic.js +++ /dev/null @@ -1,56 +0,0 @@ -import { linkTypes } from "./links.js"; - -export function loadMembers(object, data, membersToLoad) { - for (const member of membersToLoad) { - const name = member.name; - if (data[name] === undefined) continue; // load up to date data - object[name] = data[name]; - } -} - -export function loadOneToOneRelations( - object, - data, - relationsToLoad = [], - oneToOne, - objects -) { - object.oneToOneRelations = {}; - for (const relation of relationsToLoad) { - const name = relation.name; - const relationData = data[name]; - if (relationData === undefined) continue; - - const toObject = objects[relationData.index]; - const linkType = linkTypes[name]; - const link = new linkType(object, toObject); - - oneToOne[name].push(link); - object.oneToOneRelations[name] = link; - } -} - -export function loadOneToManyRelations( - object, - data, - relationsToLoad = [], - oneToMany, - objects -) { - object.oneToManyRelations = {}; - for (const relation of relationsToLoad) { - const name = relation.name; - const relationData = data[name]; - - if (relationData === undefined) continue; - object.oneToManyRelations[name] = []; - - for (const relationElement of relationData) { - const toObject = objects[relationElement.index]; - const linkType = linkTypes[name]; - const link = new linkType(object, toObject); - oneToMany[name].push(link); - object.oneToManyRelations[name].push(link); - } - } -} diff --git a/js/types/edmobject.js b/js/types/edmobject.js deleted file mode 100644 index 52332c34..00000000 --- a/js/types/edmobject.js +++ /dev/null @@ -1,8 +0,0 @@ -export class EDMObject { - constructor(id) { - this.id = id; - } - - draw() {} - // more methods common to all particles -} diff --git a/js/types/links.js b/js/types/links.js index 5f2914d1..3a550525 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -128,10 +128,19 @@ export class DaughterLink extends Link { } } +export class MCRecoParticleAssociation extends Link { + constructor(from, to, weight) { + super(from, to); + this.weight = weight; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, "trackerHits": Link, "startVertex": Link, "particles": Link, + "clusters": Link, + "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, }; diff --git a/js/types/load.js b/js/types/load.js index 1623a67d..9e809811 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -1,88 +1,228 @@ import { objectTypes } from "./objects.js"; import { datatypes } from "../../output/datatypes.js"; -import { - loadMembers, - loadOneToOneRelations, - loadOneToManyRelations, -} from "./dynamic.js"; -import { generateRandomColor, colors } from "./links.js"; - -export function loadObjectType(collection, datatype, type) { - const objects = []; - let oneToOne = {}; - if (datatype.oneToOneRelations) - datatype.oneToOneRelations.forEach((relation) => { - oneToOne[relation.name] = []; - if (colors[relation.name] === undefined) { - colors[relation.name] = generateRandomColor(); - } - }); - - let oneToMany = {}; - if (datatype.oneToManyRelations) - datatype.oneToManyRelations.forEach((relation) => { - oneToMany[relation.name] = []; - if (colors[relation.name] === undefined) { - colors[relation.name] = generateRandomColor(); - } - }); +import { linkTypes } from "./links.js"; - for (const [index, particle] of collection.entries()) { - const newObject = new type(index); +// import json from "../../input/p8_ee_ZH_ecm240_edm4hep.edm4hep.json" assert { type: "json" }; - loadMembers(newObject, particle, datatype.members); +function loadMembers(object, data, membersToLoad) { + for (const member of membersToLoad) { + const name = member.name; + if (data[name] === undefined) continue; // load up to date data + object[name] = data[name]; + } +} - objects.push(newObject); +function loadEmptyRelations(object, relations) { + const oneToOneRelations = relations.oneToOneRelations ?? []; + if (oneToOneRelations) object.oneToOneRelations = {}; + // for (const { name } of oneToOneRelations) { + // object.oneToOneRelations[name] = null; + // } + + const oneToManyRelations = relations.oneToManyRelations ?? []; + if (oneToManyRelations) object.oneToManyRelations = {}; + for (const { name } of oneToManyRelations) { + object.oneToManyRelations[name] = []; } +} + +export function loadPlainObject(collection, datatype, collectionId) { + const objects = []; for (const [index, particle] of collection.entries()) { - const newObject = objects[index]; - - loadOneToOneRelations( - newObject, - particle, - datatype.oneToOneRelations, - oneToOne, - objects - ); - - loadOneToManyRelations( - newObject, - particle, - datatype.oneToManyRelations, - oneToMany, - objects - ); + const newObject = new objectTypes[datatype](); + newObject.index = index; + newObject.collectionId = collectionId; + + loadMembers(newObject, particle, datatypes[datatype].members); + loadEmptyRelations(newObject, datatypes[datatype]); + + objects.push(newObject); } - return [objects, oneToOne, oneToMany]; + return objects; } export function loadObjects(jsonData, event, objectsToLoad) { const eventData = jsonData["Event " + event]; - const objects = {}; + const datatypesToLoad = objectsToLoad.filter( + (object) => !object.includes("Association") + ); + const associations = objectsToLoad.filter((object) => + object.includes("Association") + ); + + const objects = { + "datatypes": {}, + "associations": {}, + }; + + datatypesToLoad.forEach((datatype) => { + objects.datatypes[datatype] = { + collection: [], + oneToMany: {}, + oneToOne: {}, + }; + }); + + associations.forEach((association) => { + objects.associations[association] = []; + }); + + for (const datatype of datatypesToLoad) { + Object.values(eventData).forEach((element) => { + const collectionName = `${datatype}Collection`; + if (element.collType === collectionName) { + const collection = element.collection; + const collectionId = element.collID; + const objectCollection = loadPlainObject( + collection, + datatype, + collectionId + ); + objects.datatypes[datatype].collection.push(...objectCollection); + } + }); + } - for (const type of objectsToLoad) { - let collectionType = Object.values(eventData).filter( - (element) => element.collType === `${type}Collection` - ); + for (const datatype of datatypesToLoad) { + const oneToOneRelations = datatypes?.[datatype]?.oneToOneRelations ?? []; + oneToOneRelations.forEach((relation) => { + objects.datatypes[datatype].oneToOne[relation.name] = []; + }); - collectionType = collectionType.map((coll) => coll.collection); - collectionType = collectionType.flat(); + const oneToManyRelations = datatypes?.[datatype]?.oneToManyRelations ?? []; + oneToManyRelations.forEach((relation) => { + objects.datatypes[datatype].oneToMany[relation.name] = []; + }); - const [loadedCollection, oneToOne, oneToMany] = loadObjectType( - collectionType, - datatypes[type], - objectTypes[type] - ); + Object.values(eventData).forEach((element) => { + const collectionName = `${datatype}Collection`; + if (element.collType === collectionName) { + const fromCollection = objects.datatypes[datatype].collection.filter( + (object) => object.collectionId === element.collID + ); + + // load One To One Relations + for (const { type, name } of oneToOneRelations) { + if (objects.datatypes?.[type] === undefined) continue; + const oneToOneRelationData = element.collection.map( + (object) => object[name] + ); + const toCollectionID = + oneToOneRelationData.find( + (relation) => relation.collectionID !== undefined + ).collectionID ?? NaN; + + const toCollection = objects.datatypes[type].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + if (toCollection) { + for (const [index, relation] of oneToOneRelationData.entries()) { + if (relation.index < 0) continue; + const fromObject = fromCollection[index]; + const toObject = toCollection[relation.index]; + + const linkType = linkTypes[name]; + const link = new linkType(fromObject, toObject); + fromObject.oneToOneRelations[name] = link; + objects.datatypes[datatype].oneToOne[name].push(link); + } + } + } + + // load One To Many Relations + for (const { type, name } of oneToManyRelations) { + if (objects.datatypes?.[type] === undefined) continue; + const oneToManyRelationData = element.collection.map( + (object) => object[name] + ); + + const toCollectionID = + oneToManyRelationData.find( + (relation) => relation?.[0]?.collectionID !== undefined + )?.[0]?.collectionID ?? NaN; + const toCollection = objects.datatypes[type].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + if (toCollection) { + for (const [index, relation] of oneToManyRelationData.entries()) { + if (relation.length === 0) continue; + const fromObject = fromCollection[index]; + for (const relationElement of relation) { + if (relationElement.index < 0) continue; + const toObject = toCollection[relationElement.index]; + + const linkType = linkTypes[name]; + const link = new linkType(fromObject, toObject); + fromObject.oneToManyRelations[name].push(link); + objects.datatypes[datatype].oneToMany[name].push(link); + } + } + } + } + } + }); + } - objects[type] = { - collection: loadedCollection, - oneToMany: oneToMany, - oneToOne: oneToOne, - }; + // Currently, all associations are one-to-one + for (const association of associations) { + Object.values(eventData).forEach((element) => { + const collectionName = `${association}Collection`; + if (element.collType === collectionName) { + const collection = element.collection; + if (collection.length === 0) return; + + const { type: fromType, name: fromName } = + datatypes[association].oneToOneRelations[0]; + const { type: toType, name: toName } = + datatypes[association].oneToOneRelations[1]; + + const fromCollectionID = collection.find( + (relation) => relation[fromName].collectionID !== undefined + )[fromName].collectionID; + const toCollectionID = collection.find( + (relation) => relation[toName].collectionID !== undefined + )[toName].collectionID; + + const fromCollection = objects.datatypes[fromType].collection.filter( + (object) => object.collectionId === fromCollectionID + ); + const toCollection = objects.datatypes[toType].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + for (const associationElement of collection) { + const fromObject = fromCollection[associationElement[fromName].index]; + const toObject = toCollection[associationElement[toName].index]; + + const linkType = linkTypes[association]; + const link = new linkType( + fromObject, + toObject, + associationElement.weight + ); + objects.associations[association].push(link); + fromObject.associations = {}; + fromObject.associations[association] = link; + toObject.associations = {}; + toObject.associations[association] = link; + } + } + }); } 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 d0fe160f..55c8a8ee 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -1,50 +1,76 @@ -import { EDMObject } from "./edmobject.js"; import { drawTex, drawRoundedRect } from "../graphic-primitives.js"; import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; +class EDMObject { + constructor() { + this.x = 0; + this.y = 0; + this.width = 120; + this.height = 240; + this.lineColor = "black"; + this.lineWidth = 2; + this.color = "white"; + } + + draw(ctx) {} + + isHere(mouseX, mouseY) { + return ( + mouseX > this.x && + mouseX < this.x + this.width && + mouseY > this.y && + mouseY < this.y + this.height + ); + } + + isVisible(x, y, width, height) { + return ( + x + width > this.x && + x < this.x + this.width && + y + height > this.y && + y < this.y + this.height + ); + } + // more methods common to all particles +} + export class Cluster extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class ParticleID extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class ReconstructedParticle extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } + + static setup() {} } export class Vertex extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class Track extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class MCParticle extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); - // Appearance - this.x = 0; - this.y = 0; - this.width = 120; - this.height = 240; - this.lineColor = "black"; - this.lineWidth = 2; - this.color = "white"; this.row = -1; this.texImg = null; @@ -63,8 +89,6 @@ export class MCParticle extends EDMObject { } draw(ctx) { - // drawCross(ctx, this.x, this.y); - const boxCenterX = this.x + this.width / 2; drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); @@ -91,7 +115,7 @@ export class MCParticle extends EDMObject { const topY = this.y + 20; const topLines = []; - topLines.push("ID: " + this.id); + topLines.push("ID: " + this.index); topLines.push("Gen. stat.: " + this.generatorStatus); topLines.push("Sim. stat.: " + this.simulatorStatus); @@ -138,24 +162,6 @@ export class MCParticle extends EDMObject { ctx.restore(); } - isHere(mouseX, mouseY) { - return ( - mouseX > this.x && - mouseX < this.x + this.width && - mouseY > this.y && - mouseY < this.y + this.height - ); - } - - isVisible(x, y, width, height) { - return ( - x + width > this.x && - x < this.x + this.width && - y + height > this.y && - y < this.y + this.height - ); - } - static setup(mcCollection, canvas) { for (const mcParticle of mcCollection) { const parentLength = mcParticle.oneToManyRelations["parents"].length; diff --git a/model/index.js b/model/index.js index 1670a059..cfd58d44 100644 --- a/model/index.js +++ b/model/index.js @@ -17,17 +17,15 @@ const configTypes = new Set([ "edm4hep::Vertex", "edm4hep::ReconstructedParticle", "edm4hep::Track", + "edm4hep::MCRecoParticleAssociation", ]); const selectedTypes = Object.entries(datatypes).filter(([key, _]) => configTypes.has(key) ); -const componentsDefinition = {}; const datatypesDefinition = {}; -class Component {} - class DataTypeMember { constructor(name, unit = null) { this.name = name; @@ -36,7 +34,8 @@ class DataTypeMember { } class Relation { - constructor(name) { + constructor(type, name) { + this.type = type; this.name = name; } } @@ -63,9 +62,9 @@ const parseDatatypesMembers = (members) => { const parseRelation = (relations) => { return relations.map((relation) => { - const [_, name] = parseString(relation); + const [type, name] = parseString(relation); - return new Relation(name); + return new Relation(type, name); }); }; diff --git a/output/datatypes.js b/output/datatypes.js index 942da49d..bbd0c3e0 100644 --- a/output/datatypes.js +++ b/output/datatypes.js @@ -46,9 +46,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::MCParticle", "name": "parents" }, { + "type": "edm4hep::MCParticle", "name": "daughters" } ] @@ -70,6 +72,7 @@ export const datatypes = { ], "oneToOneRelations": [ { + "type": "edm4hep::ReconstructedParticle", "name": "particle" } ] @@ -107,9 +110,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::Cluster", "name": "clusters" }, { + "type": "edm4hep::CalorimeterHit", "name": "hits" } ] @@ -137,9 +142,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::TrackerHit", "name": "trackerHits" }, { + "type": "edm4hep::Track", "name": "tracks" } ] @@ -167,6 +174,7 @@ export const datatypes = { ], "oneToOneRelations": [ { + "type": "edm4hep::ReconstructedParticle", "name": "associatedParticle" } ] @@ -204,19 +212,40 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::Cluster", "name": "clusters" }, { + "type": "edm4hep::Track", "name": "tracks" }, { + "type": "edm4hep::ReconstructedParticle", "name": "particles" } ], "oneToOneRelations": [ { + "type": "edm4hep::Vertex", "name": "startVertex" } ] + }, + "edm4hep::MCRecoParticleAssociation": { + "members": [ + { + "name": "weight" + } + ], + "oneToOneRelations": [ + { + "type": "edm4hep::ReconstructedParticle", + "name": "rec" + }, + { + "type": "edm4hep::MCParticle", + "name": "sim" + } + ] } } \ No newline at end of file From 8f6f45d4619d78e39cc538b29fa708f0b9422245 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 24 Jun 2024 20:13:41 -0500 Subject: [PATCH 03/21] add more links and begin with random positions --- js/draw.js | 11 ++++++++++- js/events.js | 24 ++++++++++++++++++++++-- js/graph/random-positions.js | 17 +++++++++++++++++ js/lib/copy.js | 2 +- js/menu/event-number.js | 5 +++-- js/place-objects.js | 18 +++++++++++++++--- js/types/links.js | 13 +++++++++++-- js/types/load.js | 3 --- js/types/objects.js | 8 ++++++-- 9 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 js/graph/random-positions.js diff --git a/js/draw.js b/js/draw.js index 7cf6ba9d..b82a7b4f 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,7 +1,16 @@ import { canvas, ctx } from "./main.js"; function draw(objects) { - for (const elements of Object.values(objects.datatypes ?? {})) { + const datatypes = objects.datatypes; + const associations = objects.associations; + + for (const collection of Object.values(associations)) { + for (const association of collection) { + association.draw(ctx); + } + } + + for (const elements of Object.values(datatypes ?? {})) { const { collection, oneToMany, oneToOne } = elements; for (const links of Object.values(oneToMany)) { diff --git a/js/events.js b/js/events.js index b06e90b3..862a3d1a 100644 --- a/js/events.js +++ b/js/events.js @@ -72,6 +72,7 @@ const getVisible = function (loadedObjects, visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); visibleObjects.datatypes = {}; + visibleObjects.associations = {}; for (const [objectType, elements] of Object.entries( loadedObjects.datatypes ?? {} )) { @@ -114,7 +115,7 @@ const getVisible = function (loadedObjects, visibleObjects) { } for (const [name, links] of Object.entries(oneToOne)) { - visibleObjects.datatypes[objectType].oneToOne[name] = null; + visibleObjects.datatypes[objectType].oneToOne[name] = []; for (const link of links) { if ( @@ -125,11 +126,30 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects.datatypes[objectType].oneToOne[name] = link; + visibleObjects.datatypes[objectType].oneToOne[name].push(link); } } } } + + for (const [name, links] of Object.entries( + loadedObjects.associations ?? {} + )) { + visibleObjects.associations[name] = []; + + for (const link of links) { + if ( + link.isVisible( + 0 - boundigClientRect.x, + 0 - boundigClientRect.y, + window.innerWidth, + window.innerHeight + ) + ) { + visibleObjects.associations[name].push(link); + } + } + } }; const onScroll = function (currentObjects, visibleObjects) { diff --git a/js/graph/random-positions.js b/js/graph/random-positions.js new file mode 100644 index 00000000..d2aecdc5 --- /dev/null +++ b/js/graph/random-positions.js @@ -0,0 +1,17 @@ +import { canvas } from "../main.js"; + +function randomNumber(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); +} + +export function generateRandomPositions(nodes) { + const width = canvas.width; + const height = canvas.height; + + for (const node of nodes) { + if (isNaN(node.x) && isNaN(node.y)) { + node.x = randomNumber(0, width); + node.y = randomNumber(0, height); + } + } +} diff --git a/js/lib/copy.js b/js/lib/copy.js index f87cbc03..b485be77 100644 --- a/js/lib/copy.js +++ b/js/lib/copy.js @@ -19,7 +19,7 @@ export function emptyCopyObject(objToCopy, updatedObject) { } for (const name in oneToOne) { - updatedObject[objectType].oneToOne[name] = null; + updatedObject[objectType].oneToOne[name] = []; } } } diff --git a/js/menu/event-number.js b/js/menu/event-number.js index d4fa8b87..fd89423c 100644 --- a/js/menu/event-number.js +++ b/js/menu/event-number.js @@ -18,7 +18,7 @@ import { import { drawAll } from "../draw.js"; import { objectTypes } from "../types/objects.js"; import { jsonData, selectedObjectTypes } from "../main.js"; -import { placeObjects } from "../place-objects.js"; +import { placeObjects, applyNewPositions } from "../place-objects.js"; const filters = document.getElementById("filters"); const eventNumber = document.getElementById("selected-event"); @@ -74,7 +74,8 @@ function loadSelectedEvent() { // Prepare objects for drawing // if (!layoutObjects[currentEvent]) { - placeObjects(currentObjects); + const nodes = placeObjects(currentObjects); + applyNewPositions(currentObjects, nodes); // layoutObjects[currentEvent] = true; // } diff --git a/js/place-objects.js b/js/place-objects.js index 0b64ce05..d1880aa0 100644 --- a/js/place-objects.js +++ b/js/place-objects.js @@ -1,4 +1,5 @@ import { fruchtermanReingold } from "./graph/fruchrein.js"; +import { generateRandomPositions } from "./graph/random-positions.js"; function objectToNode(object) { const edges = []; @@ -24,8 +25,6 @@ function objectToNode(object) { return { x: object.x, y: object.y, - width: object.width, - height: object.height, edges, }; } @@ -59,7 +58,20 @@ export function placeObjects(objects) { } } + generateRandomPositions(nodes); + console.log(nodes, edges); - fruchtermanReingold(nodes, edges); + return nodes; +} + +export function applyNewPositions(objects, nodes) { + let index = 0; + Object.values(objects.datatypes).forEach(({ collection }) => { + collection.forEach((obj) => { + obj.x = nodes[index].x; + obj.y = nodes[index].y; + index++; + }); + }); } diff --git a/js/types/links.js b/js/types/links.js index 3a550525..be057816 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -1,7 +1,8 @@ export const colors = { "daughters": "#00AA00", "parents": "#AA0000", - // more if needed + "mcreco": "#0000AA", + "clusters": "#AA00AA", }; export function generateRandomColor() { @@ -131,16 +132,24 @@ export class DaughterLink extends Link { export class MCRecoParticleAssociation extends Link { constructor(from, to, weight) { super(from, to); + this.color = colors["mcreco"]; this.weight = weight; } } +export class Particles extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["clusters"]; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, "trackerHits": Link, "startVertex": Link, - "particles": Link, + "particles": Particles, "clusters": Link, "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, }; diff --git a/js/types/load.js b/js/types/load.js index 9e809811..0e5af762 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -15,9 +15,6 @@ function loadMembers(object, data, membersToLoad) { function loadEmptyRelations(object, relations) { const oneToOneRelations = relations.oneToOneRelations ?? []; if (oneToOneRelations) object.oneToOneRelations = {}; - // for (const { name } of oneToOneRelations) { - // object.oneToOneRelations[name] = null; - // } const oneToManyRelations = relations.oneToManyRelations ?? []; if (oneToManyRelations) object.oneToManyRelations = {}; diff --git a/js/types/objects.js b/js/types/objects.js index 55c8a8ee..c3070c37 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -4,8 +4,8 @@ import { linkTypes } from "./links.js"; class EDMObject { constructor() { - this.x = 0; - this.y = 0; + this.x = NaN; + this.y = NaN; this.width = 120; this.height = 240; this.lineColor = "black"; @@ -52,6 +52,10 @@ export class ReconstructedParticle extends EDMObject { super(); } + draw(ctx) { + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + } + static setup() {} } From b66f72d0019cc628aa631fd7d328365ec8cabb83 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sun, 23 Jun 2024 21:51:10 -0500 Subject: [PATCH 04/21] feat: fix loading and prepare objects to be placed on canvas by algorithm --- js/draw.js | 38 ++--- js/events.js | 19 +-- js/graph/fruchrein.js | 4 + js/main.js | 16 ++- js/menu/event-number.js | 70 +++++----- js/menu/filter/reconnect.js | 2 +- js/place-objects.js | 65 +++++++++ js/types/dynamic.js | 56 -------- js/types/edmobject.js | 8 -- js/types/links.js | 9 ++ js/types/load.js | 270 +++++++++++++++++++++++++++--------- js/types/objects.js | 90 ++++++------ model/index.js | 11 +- output/datatypes.js | 29 ++++ 14 files changed, 442 insertions(+), 245 deletions(-) create mode 100644 js/graph/fruchrein.js create mode 100644 js/place-objects.js delete mode 100644 js/types/dynamic.js delete mode 100644 js/types/edmobject.js diff --git a/js/draw.js b/js/draw.js index 65159e75..7cf6ba9d 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,21 +1,33 @@ import { canvas, ctx } from "./main.js"; -export function drawAll(ctx, loadedObjects) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - for (const elements of Object.values(loadedObjects)) { +function draw(objects) { + for (const elements of Object.values(objects.datatypes ?? {})) { const { collection, oneToMany, oneToOne } = elements; for (const links of Object.values(oneToMany)) { - for (const link of links) link.draw(ctx); + for (const link of links) { + link.draw(ctx); + } } - for (const link of Object.values(oneToOne)) link.draw(ctx); + for (const links of Object.values(oneToOne)) { + for (const link of links) { + link.draw(ctx); + } + } - for (const object of collection) object.draw(ctx); + for (const object of collection) { + object.draw(ctx); + } } } +export function drawAll(ctx, loadedObjects) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + draw(loadedObjects); +} + export function drawVisible(visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); ctx.clearRect( @@ -25,15 +37,5 @@ export function drawVisible(visibleObjects) { window.innerHeight ); - for (const elements of Object.values(visibleObjects)) { - const { collection, oneToMany, oneToOne } = elements; - - for (const links of Object.values(oneToMany)) { - for (const link of links) link.draw(ctx); - } - - for (const link of Object.values(oneToOne)) link.draw(ctx); - - for (const object of collection) object.draw(ctx); - } + draw(visibleObjects); } diff --git a/js/events.js b/js/events.js index 660ec57b..b06e90b3 100644 --- a/js/events.js +++ b/js/events.js @@ -10,7 +10,7 @@ const mouseDown = function (event, visibleObjects, dragTools) { dragTools.prevMouseX = mouseX; dragTools.prevMouseY = mouseY; - for (const { collection } of Object.values(visibleObjects)) { + for (const { collection } of Object.values(visibleObjects.datatypes)) { for (const object of collection) { if (object.isHere(mouseX, mouseY)) { dragTools.draggedObject = object; @@ -71,10 +71,13 @@ const mouseMove = function (event, visibleObjects, dragTools) { const getVisible = function (loadedObjects, visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); - for (const [objectType, elements] of Object.entries(loadedObjects)) { + visibleObjects.datatypes = {}; + for (const [objectType, elements] of Object.entries( + loadedObjects.datatypes ?? {} + )) { const { collection, oneToMany, oneToOne } = elements; - visibleObjects[objectType] = { + visibleObjects.datatypes[objectType] = { collection: [], oneToMany: {}, oneToOne: {}, @@ -89,12 +92,12 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].collection.push(object); + visibleObjects.datatypes[objectType].collection.push(object); } } for (const [name, links] of Object.entries(oneToMany)) { - visibleObjects[objectType].oneToMany[name] = []; + visibleObjects.datatypes[objectType].oneToMany[name] = []; for (const link of links) { if ( @@ -105,13 +108,13 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].oneToMany[name].push(link); + visibleObjects.datatypes[objectType].oneToMany[name].push(link); } } } for (const [name, links] of Object.entries(oneToOne)) { - visibleObjects[objectType].oneToOne[name] = null; + visibleObjects.datatypes[objectType].oneToOne[name] = null; for (const link of links) { if ( @@ -122,7 +125,7 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].oneToOne[name] = link; + visibleObjects.datatypes[objectType].oneToOne[name] = link; } } } diff --git a/js/graph/fruchrein.js b/js/graph/fruchrein.js new file mode 100644 index 00000000..8a7334ee --- /dev/null +++ b/js/graph/fruchrein.js @@ -0,0 +1,4 @@ +const nodeHeight = 240; +const nodeWidth = 120; + +export function fruchtermanReingold(nodes, edges) {} diff --git a/js/main.js b/js/main.js index f76ce42e..f820903f 100644 --- a/js/main.js +++ b/js/main.js @@ -3,7 +3,6 @@ import { PdgToggle } from "./menu/show-pdg.js"; import { drawAll } from "./draw.js"; import { getWidthFilterContent } from "./menu/filter/filter.js"; import { mouseDown, mouseUp, mouseOut, mouseMove, onScroll } from "./events.js"; -import { showEventSwitcher, loadSelectedEvent } from "./menu/event-number.js"; import { renderEvent } from "./menu/event-number.js"; const canvas = document.getElementById("canvas"); @@ -28,7 +27,11 @@ const currentObjects = {}; const visibleObjects = {}; const selectedObjectTypes = { - types: ["edm4hep::MCParticle"], + types: [ + "edm4hep::MCParticle", + "edm4hep::ReconstructedParticle", + "edm4hep::MCRecoParticleAssociation", + ], }; canvas.onmousedown = (event) => { @@ -53,6 +56,11 @@ function hideInputModal() { modal.style.display = "none"; } +function showEventSwitcher() { + const eventSwitcher = document.getElementById("event-switcher"); + eventSwitcher.style.display = "flex"; +} + document.getElementById("input-file").addEventListener("change", (event) => { for (const file of event.target.files) { if (!file.name.endsWith("edm4hep.json")) { @@ -110,8 +118,8 @@ document const eventNum = document.getElementById("event-number").value; hideInputModal(); - showEventSwitcher(eventNum); - loadSelectedEvent(); + showEventSwitcher(); + renderEvent(eventNum); const width = getWidthFilterContent(); filter.style.width = width; diff --git a/js/menu/event-number.js b/js/menu/event-number.js index 273a817e..d4fa8b87 100644 --- a/js/menu/event-number.js +++ b/js/menu/event-number.js @@ -18,9 +18,9 @@ import { import { drawAll } from "../draw.js"; import { objectTypes } from "../types/objects.js"; import { jsonData, selectedObjectTypes } from "../main.js"; +import { placeObjects } from "../place-objects.js"; const filters = document.getElementById("filters"); -const eventSwitcher = document.getElementById("event-switcher"); const eventNumber = document.getElementById("selected-event"); const previousEvent = document.getElementById("previous-event"); const nextEvent = document.getElementById("next-event"); @@ -30,49 +30,24 @@ let currentEvent; const scrollLocation = {}; -function updateEventNumber(newEventNumber) { +const layoutObjects = []; + +function updateEventNumber() { if (eventNumber.firstChild) { eventNumber.removeChild(eventNumber.firstChild); } - eventNumber.appendChild(document.createTextNode(`Event: ${newEventNumber}`)); -} - -function start(currentObjects, visibleObjects) { - for (const [key, value] of Object.entries(currentObjects)) { - const classType = objectTypes[key]; - const collection = value.collection; - classType.setup(collection, canvas); - } - - drawAll(ctx, currentObjects); - - getVisible(currentObjects, visibleObjects); + eventNumber.appendChild(document.createTextNode(`Event: ${currentEvent}`)); } -export function renderEvent(eventNumber) { - const data = jsonData.data[`Event ${eventNumber}`]; - +function saveScrollLocation() { + if (scrollLocation[currentEvent] === undefined) return; scrollLocation[currentEvent] = { x: window.scrollX, y: window.scrollY, }; - - if (data === undefined) { - return; - } else { - currentEvent = eventNumber; - loadSelectedEvent(jsonData, selectedObjectTypes.types, eventNumber); - updateEventNumber(eventNumber); - } -} - -export function showEventSwitcher(initialValue) { - eventSwitcher.style.display = "flex"; - updateEventNumber(initialValue); - currentEvent = initialValue; } -export function loadSelectedEvent() { +function loadSelectedEvent() { const objects = loadObjects( jsonData.data, currentEvent, @@ -82,7 +57,7 @@ export function loadSelectedEvent() { copyObject(objects, loadedObjects); copyObject(objects, currentObjects); - const length = Object.values(loadedObjects) + const length = Object.values(loadedObjects.datatypes) .map((obj) => obj.collection.length) .reduce((a, b) => a + b, 0); @@ -91,7 +66,21 @@ export function loadSelectedEvent() { return; } - start(currentObjects, visibleObjects); + for (const [key, value] of Object.entries(currentObjects.datatypes)) { + const classType = objectTypes[key]; + const collection = value.collection; + classType.setup(collection, canvas); + } + + // Prepare objects for drawing + // if (!layoutObjects[currentEvent]) { + placeObjects(currentObjects); + // layoutObjects[currentEvent] = true; + // } + + drawAll(ctx, currentObjects); + getVisible(currentObjects, visibleObjects); + if (scrollLocation[currentEvent] === undefined) { scrollLocation[currentEvent] = { x: (canvas.width - window.innerWidth) / 2, @@ -101,11 +90,11 @@ export function loadSelectedEvent() { window.scroll(scrollLocation[currentEvent].x, scrollLocation[currentEvent].y); + // menu/filtering stuff for (const tool of manipulationTools) { tool.style.display = "flex"; } - - const mcObjects = loadedObjects["edm4hep::MCParticle"].collection; + const mcObjects = loadedObjects.datatypes["edm4hep::MCParticle"].collection; genStatus.reset(); mcObjects.forEach((mcObject) => { genStatus.add(mcObject.generatorStatus); @@ -117,6 +106,13 @@ export function loadSelectedEvent() { renderGenSim(bits, genStatus); } +export function renderEvent(eventNumber) { + saveScrollLocation(); + currentEvent = eventNumber; + loadSelectedEvent(); + updateEventNumber(); +} + previousEvent.addEventListener("click", () => { const newEventNum = `${parseInt(currentEvent) - 1}`; renderEvent(newEventNum); diff --git a/js/menu/filter/reconnect.js b/js/menu/filter/reconnect.js index e33e4eee..e4ceb28f 100644 --- a/js/menu/filter/reconnect.js +++ b/js/menu/filter/reconnect.js @@ -6,7 +6,7 @@ export function reconnect(criteriaFunction, loadedObjects) { emptyCopyObject(loadedObjects, filteredObjects); - for (const [key, value] of Object.entries(loadedObjects)) { + for (const [key, value] of Object.entries(loadedObjects.datatypes)) { const filterFunction = objectTypes[key].filter; filterFunction(value, filteredObjects, criteriaFunction); diff --git a/js/place-objects.js b/js/place-objects.js new file mode 100644 index 00000000..0b64ce05 --- /dev/null +++ b/js/place-objects.js @@ -0,0 +1,65 @@ +import { fruchtermanReingold } from "./graph/fruchrein.js"; + +function objectToNode(object) { + const edges = []; + + const oneToManyRelations = object.oneToManyRelations ?? {}; + const oneToOneRelations = object.oneToOneRelations ?? {}; + const associations = object.associations ?? {}; + + for (const link of Object.values(oneToOneRelations)) { + edges.push(link); + } + + for (const link of Object.values(associations)) { + edges.push(link); + } + + for (const links of Object.values(oneToManyRelations)) { + for (const link of links) { + edges.push(link); + } + } + + return { + x: object.x, + y: object.y, + width: object.width, + height: object.height, + edges, + }; +} + +export function placeObjects(objects) { + const nodes = []; + const edges = []; + + const datatypes = objects.datatypes; + const associations = objects.associations; + + for (const { collection, oneToOne, oneToMany } of Object.values(datatypes)) { + for (const object of collection) { + nodes.push(objectToNode(object)); + } + for (const links of Object.values(oneToOne)) { + for (const link of links) { + edges.push(link); + } + } + for (const links of Object.values(oneToMany)) { + for (const link of links) { + edges.push(link); + } + } + } + + for (const collection of Object.values(associations)) { + for (const association of collection) { + edges.push(association); + } + } + + console.log(nodes, edges); + + fruchtermanReingold(nodes, edges); +} diff --git a/js/types/dynamic.js b/js/types/dynamic.js deleted file mode 100644 index fa250270..00000000 --- a/js/types/dynamic.js +++ /dev/null @@ -1,56 +0,0 @@ -import { linkTypes } from "./links.js"; - -export function loadMembers(object, data, membersToLoad) { - for (const member of membersToLoad) { - const name = member.name; - if (data[name] === undefined) continue; // load up to date data - object[name] = data[name]; - } -} - -export function loadOneToOneRelations( - object, - data, - relationsToLoad = [], - oneToOne, - objects -) { - object.oneToOneRelations = {}; - for (const relation of relationsToLoad) { - const name = relation.name; - const relationData = data[name]; - if (relationData === undefined) continue; - - const toObject = objects[relationData.index]; - const linkType = linkTypes[name]; - const link = new linkType(object, toObject); - - oneToOne[name].push(link); - object.oneToOneRelations[name] = link; - } -} - -export function loadOneToManyRelations( - object, - data, - relationsToLoad = [], - oneToMany, - objects -) { - object.oneToManyRelations = {}; - for (const relation of relationsToLoad) { - const name = relation.name; - const relationData = data[name]; - - if (relationData === undefined) continue; - object.oneToManyRelations[name] = []; - - for (const relationElement of relationData) { - const toObject = objects[relationElement.index]; - const linkType = linkTypes[name]; - const link = new linkType(object, toObject); - oneToMany[name].push(link); - object.oneToManyRelations[name].push(link); - } - } -} diff --git a/js/types/edmobject.js b/js/types/edmobject.js deleted file mode 100644 index 52332c34..00000000 --- a/js/types/edmobject.js +++ /dev/null @@ -1,8 +0,0 @@ -export class EDMObject { - constructor(id) { - this.id = id; - } - - draw() {} - // more methods common to all particles -} diff --git a/js/types/links.js b/js/types/links.js index 5f2914d1..3a550525 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -128,10 +128,19 @@ export class DaughterLink extends Link { } } +export class MCRecoParticleAssociation extends Link { + constructor(from, to, weight) { + super(from, to); + this.weight = weight; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, "trackerHits": Link, "startVertex": Link, "particles": Link, + "clusters": Link, + "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, }; diff --git a/js/types/load.js b/js/types/load.js index 1623a67d..9e809811 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -1,88 +1,228 @@ import { objectTypes } from "./objects.js"; import { datatypes } from "../../output/datatypes.js"; -import { - loadMembers, - loadOneToOneRelations, - loadOneToManyRelations, -} from "./dynamic.js"; -import { generateRandomColor, colors } from "./links.js"; - -export function loadObjectType(collection, datatype, type) { - const objects = []; - let oneToOne = {}; - if (datatype.oneToOneRelations) - datatype.oneToOneRelations.forEach((relation) => { - oneToOne[relation.name] = []; - if (colors[relation.name] === undefined) { - colors[relation.name] = generateRandomColor(); - } - }); - - let oneToMany = {}; - if (datatype.oneToManyRelations) - datatype.oneToManyRelations.forEach((relation) => { - oneToMany[relation.name] = []; - if (colors[relation.name] === undefined) { - colors[relation.name] = generateRandomColor(); - } - }); +import { linkTypes } from "./links.js"; - for (const [index, particle] of collection.entries()) { - const newObject = new type(index); +// import json from "../../input/p8_ee_ZH_ecm240_edm4hep.edm4hep.json" assert { type: "json" }; - loadMembers(newObject, particle, datatype.members); +function loadMembers(object, data, membersToLoad) { + for (const member of membersToLoad) { + const name = member.name; + if (data[name] === undefined) continue; // load up to date data + object[name] = data[name]; + } +} - objects.push(newObject); +function loadEmptyRelations(object, relations) { + const oneToOneRelations = relations.oneToOneRelations ?? []; + if (oneToOneRelations) object.oneToOneRelations = {}; + // for (const { name } of oneToOneRelations) { + // object.oneToOneRelations[name] = null; + // } + + const oneToManyRelations = relations.oneToManyRelations ?? []; + if (oneToManyRelations) object.oneToManyRelations = {}; + for (const { name } of oneToManyRelations) { + object.oneToManyRelations[name] = []; } +} + +export function loadPlainObject(collection, datatype, collectionId) { + const objects = []; for (const [index, particle] of collection.entries()) { - const newObject = objects[index]; - - loadOneToOneRelations( - newObject, - particle, - datatype.oneToOneRelations, - oneToOne, - objects - ); - - loadOneToManyRelations( - newObject, - particle, - datatype.oneToManyRelations, - oneToMany, - objects - ); + const newObject = new objectTypes[datatype](); + newObject.index = index; + newObject.collectionId = collectionId; + + loadMembers(newObject, particle, datatypes[datatype].members); + loadEmptyRelations(newObject, datatypes[datatype]); + + objects.push(newObject); } - return [objects, oneToOne, oneToMany]; + return objects; } export function loadObjects(jsonData, event, objectsToLoad) { const eventData = jsonData["Event " + event]; - const objects = {}; + const datatypesToLoad = objectsToLoad.filter( + (object) => !object.includes("Association") + ); + const associations = objectsToLoad.filter((object) => + object.includes("Association") + ); + + const objects = { + "datatypes": {}, + "associations": {}, + }; + + datatypesToLoad.forEach((datatype) => { + objects.datatypes[datatype] = { + collection: [], + oneToMany: {}, + oneToOne: {}, + }; + }); + + associations.forEach((association) => { + objects.associations[association] = []; + }); + + for (const datatype of datatypesToLoad) { + Object.values(eventData).forEach((element) => { + const collectionName = `${datatype}Collection`; + if (element.collType === collectionName) { + const collection = element.collection; + const collectionId = element.collID; + const objectCollection = loadPlainObject( + collection, + datatype, + collectionId + ); + objects.datatypes[datatype].collection.push(...objectCollection); + } + }); + } - for (const type of objectsToLoad) { - let collectionType = Object.values(eventData).filter( - (element) => element.collType === `${type}Collection` - ); + for (const datatype of datatypesToLoad) { + const oneToOneRelations = datatypes?.[datatype]?.oneToOneRelations ?? []; + oneToOneRelations.forEach((relation) => { + objects.datatypes[datatype].oneToOne[relation.name] = []; + }); - collectionType = collectionType.map((coll) => coll.collection); - collectionType = collectionType.flat(); + const oneToManyRelations = datatypes?.[datatype]?.oneToManyRelations ?? []; + oneToManyRelations.forEach((relation) => { + objects.datatypes[datatype].oneToMany[relation.name] = []; + }); - const [loadedCollection, oneToOne, oneToMany] = loadObjectType( - collectionType, - datatypes[type], - objectTypes[type] - ); + Object.values(eventData).forEach((element) => { + const collectionName = `${datatype}Collection`; + if (element.collType === collectionName) { + const fromCollection = objects.datatypes[datatype].collection.filter( + (object) => object.collectionId === element.collID + ); + + // load One To One Relations + for (const { type, name } of oneToOneRelations) { + if (objects.datatypes?.[type] === undefined) continue; + const oneToOneRelationData = element.collection.map( + (object) => object[name] + ); + const toCollectionID = + oneToOneRelationData.find( + (relation) => relation.collectionID !== undefined + ).collectionID ?? NaN; + + const toCollection = objects.datatypes[type].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + if (toCollection) { + for (const [index, relation] of oneToOneRelationData.entries()) { + if (relation.index < 0) continue; + const fromObject = fromCollection[index]; + const toObject = toCollection[relation.index]; + + const linkType = linkTypes[name]; + const link = new linkType(fromObject, toObject); + fromObject.oneToOneRelations[name] = link; + objects.datatypes[datatype].oneToOne[name].push(link); + } + } + } + + // load One To Many Relations + for (const { type, name } of oneToManyRelations) { + if (objects.datatypes?.[type] === undefined) continue; + const oneToManyRelationData = element.collection.map( + (object) => object[name] + ); + + const toCollectionID = + oneToManyRelationData.find( + (relation) => relation?.[0]?.collectionID !== undefined + )?.[0]?.collectionID ?? NaN; + const toCollection = objects.datatypes[type].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + if (toCollection) { + for (const [index, relation] of oneToManyRelationData.entries()) { + if (relation.length === 0) continue; + const fromObject = fromCollection[index]; + for (const relationElement of relation) { + if (relationElement.index < 0) continue; + const toObject = toCollection[relationElement.index]; + + const linkType = linkTypes[name]; + const link = new linkType(fromObject, toObject); + fromObject.oneToManyRelations[name].push(link); + objects.datatypes[datatype].oneToMany[name].push(link); + } + } + } + } + } + }); + } - objects[type] = { - collection: loadedCollection, - oneToMany: oneToMany, - oneToOne: oneToOne, - }; + // Currently, all associations are one-to-one + for (const association of associations) { + Object.values(eventData).forEach((element) => { + const collectionName = `${association}Collection`; + if (element.collType === collectionName) { + const collection = element.collection; + if (collection.length === 0) return; + + const { type: fromType, name: fromName } = + datatypes[association].oneToOneRelations[0]; + const { type: toType, name: toName } = + datatypes[association].oneToOneRelations[1]; + + const fromCollectionID = collection.find( + (relation) => relation[fromName].collectionID !== undefined + )[fromName].collectionID; + const toCollectionID = collection.find( + (relation) => relation[toName].collectionID !== undefined + )[toName].collectionID; + + const fromCollection = objects.datatypes[fromType].collection.filter( + (object) => object.collectionId === fromCollectionID + ); + const toCollection = objects.datatypes[toType].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + for (const associationElement of collection) { + const fromObject = fromCollection[associationElement[fromName].index]; + const toObject = toCollection[associationElement[toName].index]; + + const linkType = linkTypes[association]; + const link = new linkType( + fromObject, + toObject, + associationElement.weight + ); + objects.associations[association].push(link); + fromObject.associations = {}; + fromObject.associations[association] = link; + toObject.associations = {}; + toObject.associations[association] = link; + } + } + }); } 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 d0fe160f..55c8a8ee 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -1,50 +1,76 @@ -import { EDMObject } from "./edmobject.js"; import { drawTex, drawRoundedRect } from "../graphic-primitives.js"; import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; +class EDMObject { + constructor() { + this.x = 0; + this.y = 0; + this.width = 120; + this.height = 240; + this.lineColor = "black"; + this.lineWidth = 2; + this.color = "white"; + } + + draw(ctx) {} + + isHere(mouseX, mouseY) { + return ( + mouseX > this.x && + mouseX < this.x + this.width && + mouseY > this.y && + mouseY < this.y + this.height + ); + } + + isVisible(x, y, width, height) { + return ( + x + width > this.x && + x < this.x + this.width && + y + height > this.y && + y < this.y + this.height + ); + } + // more methods common to all particles +} + export class Cluster extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class ParticleID extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class ReconstructedParticle extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } + + static setup() {} } export class Vertex extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class Track extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class MCParticle extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); - // Appearance - this.x = 0; - this.y = 0; - this.width = 120; - this.height = 240; - this.lineColor = "black"; - this.lineWidth = 2; - this.color = "white"; this.row = -1; this.texImg = null; @@ -63,8 +89,6 @@ export class MCParticle extends EDMObject { } draw(ctx) { - // drawCross(ctx, this.x, this.y); - const boxCenterX = this.x + this.width / 2; drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); @@ -91,7 +115,7 @@ export class MCParticle extends EDMObject { const topY = this.y + 20; const topLines = []; - topLines.push("ID: " + this.id); + topLines.push("ID: " + this.index); topLines.push("Gen. stat.: " + this.generatorStatus); topLines.push("Sim. stat.: " + this.simulatorStatus); @@ -138,24 +162,6 @@ export class MCParticle extends EDMObject { ctx.restore(); } - isHere(mouseX, mouseY) { - return ( - mouseX > this.x && - mouseX < this.x + this.width && - mouseY > this.y && - mouseY < this.y + this.height - ); - } - - isVisible(x, y, width, height) { - return ( - x + width > this.x && - x < this.x + this.width && - y + height > this.y && - y < this.y + this.height - ); - } - static setup(mcCollection, canvas) { for (const mcParticle of mcCollection) { const parentLength = mcParticle.oneToManyRelations["parents"].length; diff --git a/model/index.js b/model/index.js index 1670a059..cfd58d44 100644 --- a/model/index.js +++ b/model/index.js @@ -17,17 +17,15 @@ const configTypes = new Set([ "edm4hep::Vertex", "edm4hep::ReconstructedParticle", "edm4hep::Track", + "edm4hep::MCRecoParticleAssociation", ]); const selectedTypes = Object.entries(datatypes).filter(([key, _]) => configTypes.has(key) ); -const componentsDefinition = {}; const datatypesDefinition = {}; -class Component {} - class DataTypeMember { constructor(name, unit = null) { this.name = name; @@ -36,7 +34,8 @@ class DataTypeMember { } class Relation { - constructor(name) { + constructor(type, name) { + this.type = type; this.name = name; } } @@ -63,9 +62,9 @@ const parseDatatypesMembers = (members) => { const parseRelation = (relations) => { return relations.map((relation) => { - const [_, name] = parseString(relation); + const [type, name] = parseString(relation); - return new Relation(name); + return new Relation(type, name); }); }; diff --git a/output/datatypes.js b/output/datatypes.js index 942da49d..bbd0c3e0 100644 --- a/output/datatypes.js +++ b/output/datatypes.js @@ -46,9 +46,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::MCParticle", "name": "parents" }, { + "type": "edm4hep::MCParticle", "name": "daughters" } ] @@ -70,6 +72,7 @@ export const datatypes = { ], "oneToOneRelations": [ { + "type": "edm4hep::ReconstructedParticle", "name": "particle" } ] @@ -107,9 +110,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::Cluster", "name": "clusters" }, { + "type": "edm4hep::CalorimeterHit", "name": "hits" } ] @@ -137,9 +142,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::TrackerHit", "name": "trackerHits" }, { + "type": "edm4hep::Track", "name": "tracks" } ] @@ -167,6 +174,7 @@ export const datatypes = { ], "oneToOneRelations": [ { + "type": "edm4hep::ReconstructedParticle", "name": "associatedParticle" } ] @@ -204,19 +212,40 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::Cluster", "name": "clusters" }, { + "type": "edm4hep::Track", "name": "tracks" }, { + "type": "edm4hep::ReconstructedParticle", "name": "particles" } ], "oneToOneRelations": [ { + "type": "edm4hep::Vertex", "name": "startVertex" } ] + }, + "edm4hep::MCRecoParticleAssociation": { + "members": [ + { + "name": "weight" + } + ], + "oneToOneRelations": [ + { + "type": "edm4hep::ReconstructedParticle", + "name": "rec" + }, + { + "type": "edm4hep::MCParticle", + "name": "sim" + } + ] } } \ No newline at end of file From cd887e5728df7d4b9b6269fbb011fc83181712c6 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 24 Jun 2024 20:13:41 -0500 Subject: [PATCH 05/21] add more links and begin with random positions --- js/draw.js | 11 ++++++++++- js/events.js | 24 ++++++++++++++++++++++-- js/graph/random-positions.js | 17 +++++++++++++++++ js/lib/copy.js | 2 +- js/menu/event-number.js | 5 +++-- js/place-objects.js | 18 +++++++++++++++--- js/types/links.js | 13 +++++++++++-- js/types/load.js | 3 --- js/types/objects.js | 8 ++++++-- 9 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 js/graph/random-positions.js diff --git a/js/draw.js b/js/draw.js index 7cf6ba9d..b82a7b4f 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,7 +1,16 @@ import { canvas, ctx } from "./main.js"; function draw(objects) { - for (const elements of Object.values(objects.datatypes ?? {})) { + const datatypes = objects.datatypes; + const associations = objects.associations; + + for (const collection of Object.values(associations)) { + for (const association of collection) { + association.draw(ctx); + } + } + + for (const elements of Object.values(datatypes ?? {})) { const { collection, oneToMany, oneToOne } = elements; for (const links of Object.values(oneToMany)) { diff --git a/js/events.js b/js/events.js index b06e90b3..862a3d1a 100644 --- a/js/events.js +++ b/js/events.js @@ -72,6 +72,7 @@ const getVisible = function (loadedObjects, visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); visibleObjects.datatypes = {}; + visibleObjects.associations = {}; for (const [objectType, elements] of Object.entries( loadedObjects.datatypes ?? {} )) { @@ -114,7 +115,7 @@ const getVisible = function (loadedObjects, visibleObjects) { } for (const [name, links] of Object.entries(oneToOne)) { - visibleObjects.datatypes[objectType].oneToOne[name] = null; + visibleObjects.datatypes[objectType].oneToOne[name] = []; for (const link of links) { if ( @@ -125,11 +126,30 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects.datatypes[objectType].oneToOne[name] = link; + visibleObjects.datatypes[objectType].oneToOne[name].push(link); } } } } + + for (const [name, links] of Object.entries( + loadedObjects.associations ?? {} + )) { + visibleObjects.associations[name] = []; + + for (const link of links) { + if ( + link.isVisible( + 0 - boundigClientRect.x, + 0 - boundigClientRect.y, + window.innerWidth, + window.innerHeight + ) + ) { + visibleObjects.associations[name].push(link); + } + } + } }; const onScroll = function (currentObjects, visibleObjects) { diff --git a/js/graph/random-positions.js b/js/graph/random-positions.js new file mode 100644 index 00000000..d2aecdc5 --- /dev/null +++ b/js/graph/random-positions.js @@ -0,0 +1,17 @@ +import { canvas } from "../main.js"; + +function randomNumber(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); +} + +export function generateRandomPositions(nodes) { + const width = canvas.width; + const height = canvas.height; + + for (const node of nodes) { + if (isNaN(node.x) && isNaN(node.y)) { + node.x = randomNumber(0, width); + node.y = randomNumber(0, height); + } + } +} diff --git a/js/lib/copy.js b/js/lib/copy.js index f87cbc03..b485be77 100644 --- a/js/lib/copy.js +++ b/js/lib/copy.js @@ -19,7 +19,7 @@ export function emptyCopyObject(objToCopy, updatedObject) { } for (const name in oneToOne) { - updatedObject[objectType].oneToOne[name] = null; + updatedObject[objectType].oneToOne[name] = []; } } } diff --git a/js/menu/event-number.js b/js/menu/event-number.js index d4fa8b87..fd89423c 100644 --- a/js/menu/event-number.js +++ b/js/menu/event-number.js @@ -18,7 +18,7 @@ import { import { drawAll } from "../draw.js"; import { objectTypes } from "../types/objects.js"; import { jsonData, selectedObjectTypes } from "../main.js"; -import { placeObjects } from "../place-objects.js"; +import { placeObjects, applyNewPositions } from "../place-objects.js"; const filters = document.getElementById("filters"); const eventNumber = document.getElementById("selected-event"); @@ -74,7 +74,8 @@ function loadSelectedEvent() { // Prepare objects for drawing // if (!layoutObjects[currentEvent]) { - placeObjects(currentObjects); + const nodes = placeObjects(currentObjects); + applyNewPositions(currentObjects, nodes); // layoutObjects[currentEvent] = true; // } diff --git a/js/place-objects.js b/js/place-objects.js index 0b64ce05..d1880aa0 100644 --- a/js/place-objects.js +++ b/js/place-objects.js @@ -1,4 +1,5 @@ import { fruchtermanReingold } from "./graph/fruchrein.js"; +import { generateRandomPositions } from "./graph/random-positions.js"; function objectToNode(object) { const edges = []; @@ -24,8 +25,6 @@ function objectToNode(object) { return { x: object.x, y: object.y, - width: object.width, - height: object.height, edges, }; } @@ -59,7 +58,20 @@ export function placeObjects(objects) { } } + generateRandomPositions(nodes); + console.log(nodes, edges); - fruchtermanReingold(nodes, edges); + return nodes; +} + +export function applyNewPositions(objects, nodes) { + let index = 0; + Object.values(objects.datatypes).forEach(({ collection }) => { + collection.forEach((obj) => { + obj.x = nodes[index].x; + obj.y = nodes[index].y; + index++; + }); + }); } diff --git a/js/types/links.js b/js/types/links.js index 3a550525..be057816 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -1,7 +1,8 @@ export const colors = { "daughters": "#00AA00", "parents": "#AA0000", - // more if needed + "mcreco": "#0000AA", + "clusters": "#AA00AA", }; export function generateRandomColor() { @@ -131,16 +132,24 @@ export class DaughterLink extends Link { export class MCRecoParticleAssociation extends Link { constructor(from, to, weight) { super(from, to); + this.color = colors["mcreco"]; this.weight = weight; } } +export class Particles extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["clusters"]; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, "trackerHits": Link, "startVertex": Link, - "particles": Link, + "particles": Particles, "clusters": Link, "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, }; diff --git a/js/types/load.js b/js/types/load.js index 9e809811..0e5af762 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -15,9 +15,6 @@ function loadMembers(object, data, membersToLoad) { function loadEmptyRelations(object, relations) { const oneToOneRelations = relations.oneToOneRelations ?? []; if (oneToOneRelations) object.oneToOneRelations = {}; - // for (const { name } of oneToOneRelations) { - // object.oneToOneRelations[name] = null; - // } const oneToManyRelations = relations.oneToManyRelations ?? []; if (oneToManyRelations) object.oneToManyRelations = {}; diff --git a/js/types/objects.js b/js/types/objects.js index 55c8a8ee..c3070c37 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -4,8 +4,8 @@ import { linkTypes } from "./links.js"; class EDMObject { constructor() { - this.x = 0; - this.y = 0; + this.x = NaN; + this.y = NaN; this.width = 120; this.height = 240; this.lineColor = "black"; @@ -52,6 +52,10 @@ export class ReconstructedParticle extends EDMObject { super(); } + draw(ctx) { + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + } + static setup() {} } From 67db9c22088f3395d55d692d55d271d076d22ce6 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sun, 23 Jun 2024 21:51:10 -0500 Subject: [PATCH 06/21] feat: fix loading and prepare objects to be placed on canvas by algorithm --- js/draw.js | 38 ++--- js/events.js | 19 +-- js/graph/fruchrein.js | 4 + js/main.js | 16 ++- js/menu/event-number.js | 70 +++++----- js/menu/filter/reconnect.js | 2 +- js/place-objects.js | 65 +++++++++ js/types/dynamic.js | 56 -------- js/types/edmobject.js | 8 -- js/types/links.js | 9 ++ js/types/load.js | 270 +++++++++++++++++++++++++++--------- js/types/objects.js | 90 ++++++------ model/index.js | 11 +- output/datatypes.js | 29 ++++ 14 files changed, 442 insertions(+), 245 deletions(-) create mode 100644 js/graph/fruchrein.js create mode 100644 js/place-objects.js delete mode 100644 js/types/dynamic.js delete mode 100644 js/types/edmobject.js diff --git a/js/draw.js b/js/draw.js index 65159e75..7cf6ba9d 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,21 +1,33 @@ import { canvas, ctx } from "./main.js"; -export function drawAll(ctx, loadedObjects) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - for (const elements of Object.values(loadedObjects)) { +function draw(objects) { + for (const elements of Object.values(objects.datatypes ?? {})) { const { collection, oneToMany, oneToOne } = elements; for (const links of Object.values(oneToMany)) { - for (const link of links) link.draw(ctx); + for (const link of links) { + link.draw(ctx); + } } - for (const link of Object.values(oneToOne)) link.draw(ctx); + for (const links of Object.values(oneToOne)) { + for (const link of links) { + link.draw(ctx); + } + } - for (const object of collection) object.draw(ctx); + for (const object of collection) { + object.draw(ctx); + } } } +export function drawAll(ctx, loadedObjects) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + draw(loadedObjects); +} + export function drawVisible(visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); ctx.clearRect( @@ -25,15 +37,5 @@ export function drawVisible(visibleObjects) { window.innerHeight ); - for (const elements of Object.values(visibleObjects)) { - const { collection, oneToMany, oneToOne } = elements; - - for (const links of Object.values(oneToMany)) { - for (const link of links) link.draw(ctx); - } - - for (const link of Object.values(oneToOne)) link.draw(ctx); - - for (const object of collection) object.draw(ctx); - } + draw(visibleObjects); } diff --git a/js/events.js b/js/events.js index 660ec57b..b06e90b3 100644 --- a/js/events.js +++ b/js/events.js @@ -10,7 +10,7 @@ const mouseDown = function (event, visibleObjects, dragTools) { dragTools.prevMouseX = mouseX; dragTools.prevMouseY = mouseY; - for (const { collection } of Object.values(visibleObjects)) { + for (const { collection } of Object.values(visibleObjects.datatypes)) { for (const object of collection) { if (object.isHere(mouseX, mouseY)) { dragTools.draggedObject = object; @@ -71,10 +71,13 @@ const mouseMove = function (event, visibleObjects, dragTools) { const getVisible = function (loadedObjects, visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); - for (const [objectType, elements] of Object.entries(loadedObjects)) { + visibleObjects.datatypes = {}; + for (const [objectType, elements] of Object.entries( + loadedObjects.datatypes ?? {} + )) { const { collection, oneToMany, oneToOne } = elements; - visibleObjects[objectType] = { + visibleObjects.datatypes[objectType] = { collection: [], oneToMany: {}, oneToOne: {}, @@ -89,12 +92,12 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].collection.push(object); + visibleObjects.datatypes[objectType].collection.push(object); } } for (const [name, links] of Object.entries(oneToMany)) { - visibleObjects[objectType].oneToMany[name] = []; + visibleObjects.datatypes[objectType].oneToMany[name] = []; for (const link of links) { if ( @@ -105,13 +108,13 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].oneToMany[name].push(link); + visibleObjects.datatypes[objectType].oneToMany[name].push(link); } } } for (const [name, links] of Object.entries(oneToOne)) { - visibleObjects[objectType].oneToOne[name] = null; + visibleObjects.datatypes[objectType].oneToOne[name] = null; for (const link of links) { if ( @@ -122,7 +125,7 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects[objectType].oneToOne[name] = link; + visibleObjects.datatypes[objectType].oneToOne[name] = link; } } } diff --git a/js/graph/fruchrein.js b/js/graph/fruchrein.js new file mode 100644 index 00000000..8a7334ee --- /dev/null +++ b/js/graph/fruchrein.js @@ -0,0 +1,4 @@ +const nodeHeight = 240; +const nodeWidth = 120; + +export function fruchtermanReingold(nodes, edges) {} diff --git a/js/main.js b/js/main.js index 56926690..1bf492be 100644 --- a/js/main.js +++ b/js/main.js @@ -3,7 +3,6 @@ import { PdgToggle } from "./menu/show-pdg.js"; import { drawAll } from "./draw.js"; import { getWidthFilterContent } from "./menu/filter/filter.js"; import { mouseDown, mouseUp, mouseOut, mouseMove, onScroll } from "./events.js"; -import { showEventSwitcher, loadSelectedEvent } from "./menu/event-number.js"; import { renderEvent } from "./menu/event-number.js"; const canvas = document.getElementById("canvas"); @@ -28,7 +27,11 @@ const currentObjects = {}; const visibleObjects = {}; const selectedObjectTypes = { - types: ["edm4hep::MCParticle"], + types: [ + "edm4hep::MCParticle", + "edm4hep::ReconstructedParticle", + "edm4hep::MCRecoParticleAssociation", + ], }; canvas.onmousedown = (event) => { @@ -53,6 +56,11 @@ function hideInputModal() { modal.style.display = "none"; } +function showEventSwitcher() { + const eventSwitcher = document.getElementById("event-switcher"); + eventSwitcher.style.display = "flex"; +} + document.getElementById("input-file").addEventListener("change", (event) => { for (const file of event.target.files) { if (!file.name.endsWith("edm4hep.json")) { @@ -116,8 +124,8 @@ document const eventNum = document.getElementById("event-number").value; hideInputModal(); - showEventSwitcher(eventNum); - loadSelectedEvent(); + showEventSwitcher(); + renderEvent(eventNum); const width = getWidthFilterContent(); filter.style.width = width; diff --git a/js/menu/event-number.js b/js/menu/event-number.js index 273a817e..d4fa8b87 100644 --- a/js/menu/event-number.js +++ b/js/menu/event-number.js @@ -18,9 +18,9 @@ import { import { drawAll } from "../draw.js"; import { objectTypes } from "../types/objects.js"; import { jsonData, selectedObjectTypes } from "../main.js"; +import { placeObjects } from "../place-objects.js"; const filters = document.getElementById("filters"); -const eventSwitcher = document.getElementById("event-switcher"); const eventNumber = document.getElementById("selected-event"); const previousEvent = document.getElementById("previous-event"); const nextEvent = document.getElementById("next-event"); @@ -30,49 +30,24 @@ let currentEvent; const scrollLocation = {}; -function updateEventNumber(newEventNumber) { +const layoutObjects = []; + +function updateEventNumber() { if (eventNumber.firstChild) { eventNumber.removeChild(eventNumber.firstChild); } - eventNumber.appendChild(document.createTextNode(`Event: ${newEventNumber}`)); -} - -function start(currentObjects, visibleObjects) { - for (const [key, value] of Object.entries(currentObjects)) { - const classType = objectTypes[key]; - const collection = value.collection; - classType.setup(collection, canvas); - } - - drawAll(ctx, currentObjects); - - getVisible(currentObjects, visibleObjects); + eventNumber.appendChild(document.createTextNode(`Event: ${currentEvent}`)); } -export function renderEvent(eventNumber) { - const data = jsonData.data[`Event ${eventNumber}`]; - +function saveScrollLocation() { + if (scrollLocation[currentEvent] === undefined) return; scrollLocation[currentEvent] = { x: window.scrollX, y: window.scrollY, }; - - if (data === undefined) { - return; - } else { - currentEvent = eventNumber; - loadSelectedEvent(jsonData, selectedObjectTypes.types, eventNumber); - updateEventNumber(eventNumber); - } -} - -export function showEventSwitcher(initialValue) { - eventSwitcher.style.display = "flex"; - updateEventNumber(initialValue); - currentEvent = initialValue; } -export function loadSelectedEvent() { +function loadSelectedEvent() { const objects = loadObjects( jsonData.data, currentEvent, @@ -82,7 +57,7 @@ export function loadSelectedEvent() { copyObject(objects, loadedObjects); copyObject(objects, currentObjects); - const length = Object.values(loadedObjects) + const length = Object.values(loadedObjects.datatypes) .map((obj) => obj.collection.length) .reduce((a, b) => a + b, 0); @@ -91,7 +66,21 @@ export function loadSelectedEvent() { return; } - start(currentObjects, visibleObjects); + for (const [key, value] of Object.entries(currentObjects.datatypes)) { + const classType = objectTypes[key]; + const collection = value.collection; + classType.setup(collection, canvas); + } + + // Prepare objects for drawing + // if (!layoutObjects[currentEvent]) { + placeObjects(currentObjects); + // layoutObjects[currentEvent] = true; + // } + + drawAll(ctx, currentObjects); + getVisible(currentObjects, visibleObjects); + if (scrollLocation[currentEvent] === undefined) { scrollLocation[currentEvent] = { x: (canvas.width - window.innerWidth) / 2, @@ -101,11 +90,11 @@ export function loadSelectedEvent() { window.scroll(scrollLocation[currentEvent].x, scrollLocation[currentEvent].y); + // menu/filtering stuff for (const tool of manipulationTools) { tool.style.display = "flex"; } - - const mcObjects = loadedObjects["edm4hep::MCParticle"].collection; + const mcObjects = loadedObjects.datatypes["edm4hep::MCParticle"].collection; genStatus.reset(); mcObjects.forEach((mcObject) => { genStatus.add(mcObject.generatorStatus); @@ -117,6 +106,13 @@ export function loadSelectedEvent() { renderGenSim(bits, genStatus); } +export function renderEvent(eventNumber) { + saveScrollLocation(); + currentEvent = eventNumber; + loadSelectedEvent(); + updateEventNumber(); +} + previousEvent.addEventListener("click", () => { const newEventNum = `${parseInt(currentEvent) - 1}`; renderEvent(newEventNum); diff --git a/js/menu/filter/reconnect.js b/js/menu/filter/reconnect.js index e33e4eee..e4ceb28f 100644 --- a/js/menu/filter/reconnect.js +++ b/js/menu/filter/reconnect.js @@ -6,7 +6,7 @@ export function reconnect(criteriaFunction, loadedObjects) { emptyCopyObject(loadedObjects, filteredObjects); - for (const [key, value] of Object.entries(loadedObjects)) { + for (const [key, value] of Object.entries(loadedObjects.datatypes)) { const filterFunction = objectTypes[key].filter; filterFunction(value, filteredObjects, criteriaFunction); diff --git a/js/place-objects.js b/js/place-objects.js new file mode 100644 index 00000000..0b64ce05 --- /dev/null +++ b/js/place-objects.js @@ -0,0 +1,65 @@ +import { fruchtermanReingold } from "./graph/fruchrein.js"; + +function objectToNode(object) { + const edges = []; + + const oneToManyRelations = object.oneToManyRelations ?? {}; + const oneToOneRelations = object.oneToOneRelations ?? {}; + const associations = object.associations ?? {}; + + for (const link of Object.values(oneToOneRelations)) { + edges.push(link); + } + + for (const link of Object.values(associations)) { + edges.push(link); + } + + for (const links of Object.values(oneToManyRelations)) { + for (const link of links) { + edges.push(link); + } + } + + return { + x: object.x, + y: object.y, + width: object.width, + height: object.height, + edges, + }; +} + +export function placeObjects(objects) { + const nodes = []; + const edges = []; + + const datatypes = objects.datatypes; + const associations = objects.associations; + + for (const { collection, oneToOne, oneToMany } of Object.values(datatypes)) { + for (const object of collection) { + nodes.push(objectToNode(object)); + } + for (const links of Object.values(oneToOne)) { + for (const link of links) { + edges.push(link); + } + } + for (const links of Object.values(oneToMany)) { + for (const link of links) { + edges.push(link); + } + } + } + + for (const collection of Object.values(associations)) { + for (const association of collection) { + edges.push(association); + } + } + + console.log(nodes, edges); + + fruchtermanReingold(nodes, edges); +} diff --git a/js/types/dynamic.js b/js/types/dynamic.js deleted file mode 100644 index fa250270..00000000 --- a/js/types/dynamic.js +++ /dev/null @@ -1,56 +0,0 @@ -import { linkTypes } from "./links.js"; - -export function loadMembers(object, data, membersToLoad) { - for (const member of membersToLoad) { - const name = member.name; - if (data[name] === undefined) continue; // load up to date data - object[name] = data[name]; - } -} - -export function loadOneToOneRelations( - object, - data, - relationsToLoad = [], - oneToOne, - objects -) { - object.oneToOneRelations = {}; - for (const relation of relationsToLoad) { - const name = relation.name; - const relationData = data[name]; - if (relationData === undefined) continue; - - const toObject = objects[relationData.index]; - const linkType = linkTypes[name]; - const link = new linkType(object, toObject); - - oneToOne[name].push(link); - object.oneToOneRelations[name] = link; - } -} - -export function loadOneToManyRelations( - object, - data, - relationsToLoad = [], - oneToMany, - objects -) { - object.oneToManyRelations = {}; - for (const relation of relationsToLoad) { - const name = relation.name; - const relationData = data[name]; - - if (relationData === undefined) continue; - object.oneToManyRelations[name] = []; - - for (const relationElement of relationData) { - const toObject = objects[relationElement.index]; - const linkType = linkTypes[name]; - const link = new linkType(object, toObject); - oneToMany[name].push(link); - object.oneToManyRelations[name].push(link); - } - } -} diff --git a/js/types/edmobject.js b/js/types/edmobject.js deleted file mode 100644 index 52332c34..00000000 --- a/js/types/edmobject.js +++ /dev/null @@ -1,8 +0,0 @@ -export class EDMObject { - constructor(id) { - this.id = id; - } - - draw() {} - // more methods common to all particles -} diff --git a/js/types/links.js b/js/types/links.js index 5f2914d1..3a550525 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -128,10 +128,19 @@ export class DaughterLink extends Link { } } +export class MCRecoParticleAssociation extends Link { + constructor(from, to, weight) { + super(from, to); + this.weight = weight; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, "trackerHits": Link, "startVertex": Link, "particles": Link, + "clusters": Link, + "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, }; diff --git a/js/types/load.js b/js/types/load.js index 1623a67d..9e809811 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -1,88 +1,228 @@ import { objectTypes } from "./objects.js"; import { datatypes } from "../../output/datatypes.js"; -import { - loadMembers, - loadOneToOneRelations, - loadOneToManyRelations, -} from "./dynamic.js"; -import { generateRandomColor, colors } from "./links.js"; - -export function loadObjectType(collection, datatype, type) { - const objects = []; - let oneToOne = {}; - if (datatype.oneToOneRelations) - datatype.oneToOneRelations.forEach((relation) => { - oneToOne[relation.name] = []; - if (colors[relation.name] === undefined) { - colors[relation.name] = generateRandomColor(); - } - }); - - let oneToMany = {}; - if (datatype.oneToManyRelations) - datatype.oneToManyRelations.forEach((relation) => { - oneToMany[relation.name] = []; - if (colors[relation.name] === undefined) { - colors[relation.name] = generateRandomColor(); - } - }); +import { linkTypes } from "./links.js"; - for (const [index, particle] of collection.entries()) { - const newObject = new type(index); +// import json from "../../input/p8_ee_ZH_ecm240_edm4hep.edm4hep.json" assert { type: "json" }; - loadMembers(newObject, particle, datatype.members); +function loadMembers(object, data, membersToLoad) { + for (const member of membersToLoad) { + const name = member.name; + if (data[name] === undefined) continue; // load up to date data + object[name] = data[name]; + } +} - objects.push(newObject); +function loadEmptyRelations(object, relations) { + const oneToOneRelations = relations.oneToOneRelations ?? []; + if (oneToOneRelations) object.oneToOneRelations = {}; + // for (const { name } of oneToOneRelations) { + // object.oneToOneRelations[name] = null; + // } + + const oneToManyRelations = relations.oneToManyRelations ?? []; + if (oneToManyRelations) object.oneToManyRelations = {}; + for (const { name } of oneToManyRelations) { + object.oneToManyRelations[name] = []; } +} + +export function loadPlainObject(collection, datatype, collectionId) { + const objects = []; for (const [index, particle] of collection.entries()) { - const newObject = objects[index]; - - loadOneToOneRelations( - newObject, - particle, - datatype.oneToOneRelations, - oneToOne, - objects - ); - - loadOneToManyRelations( - newObject, - particle, - datatype.oneToManyRelations, - oneToMany, - objects - ); + const newObject = new objectTypes[datatype](); + newObject.index = index; + newObject.collectionId = collectionId; + + loadMembers(newObject, particle, datatypes[datatype].members); + loadEmptyRelations(newObject, datatypes[datatype]); + + objects.push(newObject); } - return [objects, oneToOne, oneToMany]; + return objects; } export function loadObjects(jsonData, event, objectsToLoad) { const eventData = jsonData["Event " + event]; - const objects = {}; + const datatypesToLoad = objectsToLoad.filter( + (object) => !object.includes("Association") + ); + const associations = objectsToLoad.filter((object) => + object.includes("Association") + ); + + const objects = { + "datatypes": {}, + "associations": {}, + }; + + datatypesToLoad.forEach((datatype) => { + objects.datatypes[datatype] = { + collection: [], + oneToMany: {}, + oneToOne: {}, + }; + }); + + associations.forEach((association) => { + objects.associations[association] = []; + }); + + for (const datatype of datatypesToLoad) { + Object.values(eventData).forEach((element) => { + const collectionName = `${datatype}Collection`; + if (element.collType === collectionName) { + const collection = element.collection; + const collectionId = element.collID; + const objectCollection = loadPlainObject( + collection, + datatype, + collectionId + ); + objects.datatypes[datatype].collection.push(...objectCollection); + } + }); + } - for (const type of objectsToLoad) { - let collectionType = Object.values(eventData).filter( - (element) => element.collType === `${type}Collection` - ); + for (const datatype of datatypesToLoad) { + const oneToOneRelations = datatypes?.[datatype]?.oneToOneRelations ?? []; + oneToOneRelations.forEach((relation) => { + objects.datatypes[datatype].oneToOne[relation.name] = []; + }); - collectionType = collectionType.map((coll) => coll.collection); - collectionType = collectionType.flat(); + const oneToManyRelations = datatypes?.[datatype]?.oneToManyRelations ?? []; + oneToManyRelations.forEach((relation) => { + objects.datatypes[datatype].oneToMany[relation.name] = []; + }); - const [loadedCollection, oneToOne, oneToMany] = loadObjectType( - collectionType, - datatypes[type], - objectTypes[type] - ); + Object.values(eventData).forEach((element) => { + const collectionName = `${datatype}Collection`; + if (element.collType === collectionName) { + const fromCollection = objects.datatypes[datatype].collection.filter( + (object) => object.collectionId === element.collID + ); + + // load One To One Relations + for (const { type, name } of oneToOneRelations) { + if (objects.datatypes?.[type] === undefined) continue; + const oneToOneRelationData = element.collection.map( + (object) => object[name] + ); + const toCollectionID = + oneToOneRelationData.find( + (relation) => relation.collectionID !== undefined + ).collectionID ?? NaN; + + const toCollection = objects.datatypes[type].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + if (toCollection) { + for (const [index, relation] of oneToOneRelationData.entries()) { + if (relation.index < 0) continue; + const fromObject = fromCollection[index]; + const toObject = toCollection[relation.index]; + + const linkType = linkTypes[name]; + const link = new linkType(fromObject, toObject); + fromObject.oneToOneRelations[name] = link; + objects.datatypes[datatype].oneToOne[name].push(link); + } + } + } + + // load One To Many Relations + for (const { type, name } of oneToManyRelations) { + if (objects.datatypes?.[type] === undefined) continue; + const oneToManyRelationData = element.collection.map( + (object) => object[name] + ); + + const toCollectionID = + oneToManyRelationData.find( + (relation) => relation?.[0]?.collectionID !== undefined + )?.[0]?.collectionID ?? NaN; + const toCollection = objects.datatypes[type].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + if (toCollection) { + for (const [index, relation] of oneToManyRelationData.entries()) { + if (relation.length === 0) continue; + const fromObject = fromCollection[index]; + for (const relationElement of relation) { + if (relationElement.index < 0) continue; + const toObject = toCollection[relationElement.index]; + + const linkType = linkTypes[name]; + const link = new linkType(fromObject, toObject); + fromObject.oneToManyRelations[name].push(link); + objects.datatypes[datatype].oneToMany[name].push(link); + } + } + } + } + } + }); + } - objects[type] = { - collection: loadedCollection, - oneToMany: oneToMany, - oneToOne: oneToOne, - }; + // Currently, all associations are one-to-one + for (const association of associations) { + Object.values(eventData).forEach((element) => { + const collectionName = `${association}Collection`; + if (element.collType === collectionName) { + const collection = element.collection; + if (collection.length === 0) return; + + const { type: fromType, name: fromName } = + datatypes[association].oneToOneRelations[0]; + const { type: toType, name: toName } = + datatypes[association].oneToOneRelations[1]; + + const fromCollectionID = collection.find( + (relation) => relation[fromName].collectionID !== undefined + )[fromName].collectionID; + const toCollectionID = collection.find( + (relation) => relation[toName].collectionID !== undefined + )[toName].collectionID; + + const fromCollection = objects.datatypes[fromType].collection.filter( + (object) => object.collectionId === fromCollectionID + ); + const toCollection = objects.datatypes[toType].collection.filter( + (object) => object.collectionId === toCollectionID + ); + + for (const associationElement of collection) { + const fromObject = fromCollection[associationElement[fromName].index]; + const toObject = toCollection[associationElement[toName].index]; + + const linkType = linkTypes[association]; + const link = new linkType( + fromObject, + toObject, + associationElement.weight + ); + objects.associations[association].push(link); + fromObject.associations = {}; + fromObject.associations[association] = link; + toObject.associations = {}; + toObject.associations[association] = link; + } + } + }); } 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 d0fe160f..55c8a8ee 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -1,50 +1,76 @@ -import { EDMObject } from "./edmobject.js"; import { drawTex, drawRoundedRect } from "../graphic-primitives.js"; import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; +class EDMObject { + constructor() { + this.x = 0; + this.y = 0; + this.width = 120; + this.height = 240; + this.lineColor = "black"; + this.lineWidth = 2; + this.color = "white"; + } + + draw(ctx) {} + + isHere(mouseX, mouseY) { + return ( + mouseX > this.x && + mouseX < this.x + this.width && + mouseY > this.y && + mouseY < this.y + this.height + ); + } + + isVisible(x, y, width, height) { + return ( + x + width > this.x && + x < this.x + this.width && + y + height > this.y && + y < this.y + this.height + ); + } + // more methods common to all particles +} + export class Cluster extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class ParticleID extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class ReconstructedParticle extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } + + static setup() {} } export class Vertex extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class Track extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); } } export class MCParticle extends EDMObject { - constructor(id) { - super(id); + constructor() { + super(); - // Appearance - this.x = 0; - this.y = 0; - this.width = 120; - this.height = 240; - this.lineColor = "black"; - this.lineWidth = 2; - this.color = "white"; this.row = -1; this.texImg = null; @@ -63,8 +89,6 @@ export class MCParticle extends EDMObject { } draw(ctx) { - // drawCross(ctx, this.x, this.y); - const boxCenterX = this.x + this.width / 2; drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); @@ -91,7 +115,7 @@ export class MCParticle extends EDMObject { const topY = this.y + 20; const topLines = []; - topLines.push("ID: " + this.id); + topLines.push("ID: " + this.index); topLines.push("Gen. stat.: " + this.generatorStatus); topLines.push("Sim. stat.: " + this.simulatorStatus); @@ -138,24 +162,6 @@ export class MCParticle extends EDMObject { ctx.restore(); } - isHere(mouseX, mouseY) { - return ( - mouseX > this.x && - mouseX < this.x + this.width && - mouseY > this.y && - mouseY < this.y + this.height - ); - } - - isVisible(x, y, width, height) { - return ( - x + width > this.x && - x < this.x + this.width && - y + height > this.y && - y < this.y + this.height - ); - } - static setup(mcCollection, canvas) { for (const mcParticle of mcCollection) { const parentLength = mcParticle.oneToManyRelations["parents"].length; diff --git a/model/index.js b/model/index.js index 1670a059..cfd58d44 100644 --- a/model/index.js +++ b/model/index.js @@ -17,17 +17,15 @@ const configTypes = new Set([ "edm4hep::Vertex", "edm4hep::ReconstructedParticle", "edm4hep::Track", + "edm4hep::MCRecoParticleAssociation", ]); const selectedTypes = Object.entries(datatypes).filter(([key, _]) => configTypes.has(key) ); -const componentsDefinition = {}; const datatypesDefinition = {}; -class Component {} - class DataTypeMember { constructor(name, unit = null) { this.name = name; @@ -36,7 +34,8 @@ class DataTypeMember { } class Relation { - constructor(name) { + constructor(type, name) { + this.type = type; this.name = name; } } @@ -63,9 +62,9 @@ const parseDatatypesMembers = (members) => { const parseRelation = (relations) => { return relations.map((relation) => { - const [_, name] = parseString(relation); + const [type, name] = parseString(relation); - return new Relation(name); + return new Relation(type, name); }); }; diff --git a/output/datatypes.js b/output/datatypes.js index 942da49d..bbd0c3e0 100644 --- a/output/datatypes.js +++ b/output/datatypes.js @@ -46,9 +46,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::MCParticle", "name": "parents" }, { + "type": "edm4hep::MCParticle", "name": "daughters" } ] @@ -70,6 +72,7 @@ export const datatypes = { ], "oneToOneRelations": [ { + "type": "edm4hep::ReconstructedParticle", "name": "particle" } ] @@ -107,9 +110,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::Cluster", "name": "clusters" }, { + "type": "edm4hep::CalorimeterHit", "name": "hits" } ] @@ -137,9 +142,11 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::TrackerHit", "name": "trackerHits" }, { + "type": "edm4hep::Track", "name": "tracks" } ] @@ -167,6 +174,7 @@ export const datatypes = { ], "oneToOneRelations": [ { + "type": "edm4hep::ReconstructedParticle", "name": "associatedParticle" } ] @@ -204,19 +212,40 @@ export const datatypes = { ], "oneToManyRelations": [ { + "type": "edm4hep::Cluster", "name": "clusters" }, { + "type": "edm4hep::Track", "name": "tracks" }, { + "type": "edm4hep::ReconstructedParticle", "name": "particles" } ], "oneToOneRelations": [ { + "type": "edm4hep::Vertex", "name": "startVertex" } ] + }, + "edm4hep::MCRecoParticleAssociation": { + "members": [ + { + "name": "weight" + } + ], + "oneToOneRelations": [ + { + "type": "edm4hep::ReconstructedParticle", + "name": "rec" + }, + { + "type": "edm4hep::MCParticle", + "name": "sim" + } + ] } } \ No newline at end of file From 34c8c0cb35e71b205327660f1354f65a60760280 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 24 Jun 2024 20:13:41 -0500 Subject: [PATCH 07/21] add more links and begin with random positions --- js/draw.js | 11 ++++++++++- js/events.js | 24 ++++++++++++++++++++++-- js/graph/random-positions.js | 17 +++++++++++++++++ js/lib/copy.js | 2 +- js/menu/event-number.js | 5 +++-- js/place-objects.js | 18 +++++++++++++++--- js/types/links.js | 13 +++++++++++-- js/types/load.js | 3 --- js/types/objects.js | 8 ++++++-- 9 files changed, 85 insertions(+), 16 deletions(-) create mode 100644 js/graph/random-positions.js diff --git a/js/draw.js b/js/draw.js index 7cf6ba9d..b82a7b4f 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,7 +1,16 @@ import { canvas, ctx } from "./main.js"; function draw(objects) { - for (const elements of Object.values(objects.datatypes ?? {})) { + const datatypes = objects.datatypes; + const associations = objects.associations; + + for (const collection of Object.values(associations)) { + for (const association of collection) { + association.draw(ctx); + } + } + + for (const elements of Object.values(datatypes ?? {})) { const { collection, oneToMany, oneToOne } = elements; for (const links of Object.values(oneToMany)) { diff --git a/js/events.js b/js/events.js index b06e90b3..862a3d1a 100644 --- a/js/events.js +++ b/js/events.js @@ -72,6 +72,7 @@ const getVisible = function (loadedObjects, visibleObjects) { const boundigClientRect = canvas.getBoundingClientRect(); visibleObjects.datatypes = {}; + visibleObjects.associations = {}; for (const [objectType, elements] of Object.entries( loadedObjects.datatypes ?? {} )) { @@ -114,7 +115,7 @@ const getVisible = function (loadedObjects, visibleObjects) { } for (const [name, links] of Object.entries(oneToOne)) { - visibleObjects.datatypes[objectType].oneToOne[name] = null; + visibleObjects.datatypes[objectType].oneToOne[name] = []; for (const link of links) { if ( @@ -125,11 +126,30 @@ const getVisible = function (loadedObjects, visibleObjects) { window.innerHeight ) ) { - visibleObjects.datatypes[objectType].oneToOne[name] = link; + visibleObjects.datatypes[objectType].oneToOne[name].push(link); } } } } + + for (const [name, links] of Object.entries( + loadedObjects.associations ?? {} + )) { + visibleObjects.associations[name] = []; + + for (const link of links) { + if ( + link.isVisible( + 0 - boundigClientRect.x, + 0 - boundigClientRect.y, + window.innerWidth, + window.innerHeight + ) + ) { + visibleObjects.associations[name].push(link); + } + } + } }; const onScroll = function (currentObjects, visibleObjects) { diff --git a/js/graph/random-positions.js b/js/graph/random-positions.js new file mode 100644 index 00000000..d2aecdc5 --- /dev/null +++ b/js/graph/random-positions.js @@ -0,0 +1,17 @@ +import { canvas } from "../main.js"; + +function randomNumber(min, max) { + return Math.floor(Math.random() * (max - min + 1) + min); +} + +export function generateRandomPositions(nodes) { + const width = canvas.width; + const height = canvas.height; + + for (const node of nodes) { + if (isNaN(node.x) && isNaN(node.y)) { + node.x = randomNumber(0, width); + node.y = randomNumber(0, height); + } + } +} diff --git a/js/lib/copy.js b/js/lib/copy.js index f87cbc03..b485be77 100644 --- a/js/lib/copy.js +++ b/js/lib/copy.js @@ -19,7 +19,7 @@ export function emptyCopyObject(objToCopy, updatedObject) { } for (const name in oneToOne) { - updatedObject[objectType].oneToOne[name] = null; + updatedObject[objectType].oneToOne[name] = []; } } } diff --git a/js/menu/event-number.js b/js/menu/event-number.js index d4fa8b87..fd89423c 100644 --- a/js/menu/event-number.js +++ b/js/menu/event-number.js @@ -18,7 +18,7 @@ import { import { drawAll } from "../draw.js"; import { objectTypes } from "../types/objects.js"; import { jsonData, selectedObjectTypes } from "../main.js"; -import { placeObjects } from "../place-objects.js"; +import { placeObjects, applyNewPositions } from "../place-objects.js"; const filters = document.getElementById("filters"); const eventNumber = document.getElementById("selected-event"); @@ -74,7 +74,8 @@ function loadSelectedEvent() { // Prepare objects for drawing // if (!layoutObjects[currentEvent]) { - placeObjects(currentObjects); + const nodes = placeObjects(currentObjects); + applyNewPositions(currentObjects, nodes); // layoutObjects[currentEvent] = true; // } diff --git a/js/place-objects.js b/js/place-objects.js index 0b64ce05..d1880aa0 100644 --- a/js/place-objects.js +++ b/js/place-objects.js @@ -1,4 +1,5 @@ import { fruchtermanReingold } from "./graph/fruchrein.js"; +import { generateRandomPositions } from "./graph/random-positions.js"; function objectToNode(object) { const edges = []; @@ -24,8 +25,6 @@ function objectToNode(object) { return { x: object.x, y: object.y, - width: object.width, - height: object.height, edges, }; } @@ -59,7 +58,20 @@ export function placeObjects(objects) { } } + generateRandomPositions(nodes); + console.log(nodes, edges); - fruchtermanReingold(nodes, edges); + return nodes; +} + +export function applyNewPositions(objects, nodes) { + let index = 0; + Object.values(objects.datatypes).forEach(({ collection }) => { + collection.forEach((obj) => { + obj.x = nodes[index].x; + obj.y = nodes[index].y; + index++; + }); + }); } diff --git a/js/types/links.js b/js/types/links.js index 3a550525..be057816 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -1,7 +1,8 @@ export const colors = { "daughters": "#00AA00", "parents": "#AA0000", - // more if needed + "mcreco": "#0000AA", + "clusters": "#AA00AA", }; export function generateRandomColor() { @@ -131,16 +132,24 @@ export class DaughterLink extends Link { export class MCRecoParticleAssociation extends Link { constructor(from, to, weight) { super(from, to); + this.color = colors["mcreco"]; this.weight = weight; } } +export class Particles extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["clusters"]; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, "trackerHits": Link, "startVertex": Link, - "particles": Link, + "particles": Particles, "clusters": Link, "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, }; diff --git a/js/types/load.js b/js/types/load.js index 9e809811..0e5af762 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -15,9 +15,6 @@ function loadMembers(object, data, membersToLoad) { function loadEmptyRelations(object, relations) { const oneToOneRelations = relations.oneToOneRelations ?? []; if (oneToOneRelations) object.oneToOneRelations = {}; - // for (const { name } of oneToOneRelations) { - // object.oneToOneRelations[name] = null; - // } const oneToManyRelations = relations.oneToManyRelations ?? []; if (oneToManyRelations) object.oneToManyRelations = {}; diff --git a/js/types/objects.js b/js/types/objects.js index 55c8a8ee..c3070c37 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -4,8 +4,8 @@ import { linkTypes } from "./links.js"; class EDMObject { constructor() { - this.x = 0; - this.y = 0; + this.x = NaN; + this.y = NaN; this.width = 120; this.height = 240; this.lineColor = "black"; @@ -52,6 +52,10 @@ export class ReconstructedParticle extends EDMObject { super(); } + draw(ctx) { + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + } + static setup() {} } From ac1632e9370ea90357eaa7d5f0239588c06bd33d Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 25 Jun 2024 20:17:13 -0500 Subject: [PATCH 08/21] fix filtering with new logic and test --- js/lib/copy.js | 16 ++- js/menu/filter/reconnect.js | 2 +- js/types/load.js | 11 -- js/types/objects.js | 2 + test/dynamic.test.js | 247 ---------------------------------- test/filterMCParticle.test.js | 50 +++---- test/load.json | 2 +- test/load.test.js | 216 +++-------------------------- test/objects.test.js | 6 + 9 files changed, 69 insertions(+), 483 deletions(-) delete mode 100644 test/dynamic.test.js diff --git a/js/lib/copy.js b/js/lib/copy.js index b485be77..9cf919b7 100644 --- a/js/lib/copy.js +++ b/js/lib/copy.js @@ -5,21 +5,29 @@ export function copyObject(objToCopy, updatedObject) { } export function emptyCopyObject(objToCopy, updatedObject) { - for (const [objectType, elements] of Object.entries(objToCopy)) { + updatedObject.datatypes = {}; + + for (const [objectType, elements] of Object.entries(objToCopy.datatypes)) { const { _, oneToMany, oneToOne } = elements; - updatedObject[objectType] = { + updatedObject.datatypes[objectType] = { collection: [], oneToMany: {}, oneToOne: {}, }; for (const name in oneToMany) { - updatedObject[objectType].oneToMany[name] = []; + updatedObject.datatypes[objectType].oneToMany[name] = []; } for (const name in oneToOne) { - updatedObject[objectType].oneToOne[name] = []; + updatedObject.datatypes[objectType].oneToOne[name] = []; } } + + updatedObject.associations = {}; + + for (const key in objToCopy.associations) { + updatedObject.associations[key] = []; + } } diff --git a/js/menu/filter/reconnect.js b/js/menu/filter/reconnect.js index e4ceb28f..4ff272b6 100644 --- a/js/menu/filter/reconnect.js +++ b/js/menu/filter/reconnect.js @@ -9,7 +9,7 @@ export function reconnect(criteriaFunction, loadedObjects) { for (const [key, value] of Object.entries(loadedObjects.datatypes)) { const filterFunction = objectTypes[key].filter; - filterFunction(value, filteredObjects, criteriaFunction); + filterFunction(value, filteredObjects.datatypes, criteriaFunction); } return filteredObjects; diff --git a/js/types/load.js b/js/types/load.js index 0e5af762..d2b12bd7 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -2,8 +2,6 @@ import { objectTypes } from "./objects.js"; import { datatypes } from "../../output/datatypes.js"; import { linkTypes } from "./links.js"; -// import json from "../../input/p8_ee_ZH_ecm240_edm4hep.edm4hep.json" assert { type: "json" }; - function loadMembers(object, data, membersToLoad) { for (const member of membersToLoad) { const name = member.name; @@ -214,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 c3070c37..5dba612e 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -57,6 +57,8 @@ export class ReconstructedParticle extends EDMObject { } static setup() {} + + static filter() {} } export class Vertex extends EDMObject { diff --git a/test/dynamic.test.js b/test/dynamic.test.js deleted file mode 100644 index 10c2d7dd..00000000 --- a/test/dynamic.test.js +++ /dev/null @@ -1,247 +0,0 @@ -import { - loadMembers, - loadOneToOneRelations, - loadOneToManyRelations, -} from "../js/types/dynamic.js"; - -let object; -let data; - -beforeEach(() => { - object = { - "id": 1, - }; - data = {}; -}); - -test("load members given some defined members and data", () => { - const members = [ - { - "name": "type", - }, - { - "name": "chi2", - }, - { - "name": "ndf", - }, - { - "name": "dEdx", - }, - { - "name": "dEdxError", - }, - { - "name": "radiusOfInnermostHit", - }, - ]; - data = { - "chi2": 0.0, - "dEdx": 0.0, - "dEdxError": 0.0, - "dxQuantities": [ - { - "error": 0.0, - "type": 0, - "value": 0.0, - }, - ], - "ndf": 0, - "radiusOfInnermostHit": 17.0, - "subDetectorHitNumbers": [], - "trackStates": [ - { - "D0": 0.13514114916324615, - "Z0": -0.10983038693666458, - "covMatrix": [ - 0.01773529127240181, -0.0011217461433261633, 7.128114521037787e-5, - 2.307129989276291e-7, -1.38431239804504e-8, 1.2183726250114546e-10, - 9.953266999218613e-5, -6.313419817161048e-6, 2.394112907921908e-9, - 0.019905241206288338, -3.138819374726154e-5, 1.986780489460216e-6, - -4.870325809314124e-10, -0.001264480990357697, 8.076488302322105e-5, - 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - ], - "location": 0, - "omega": 0.004053603857755661, - "phi": -0.8681905269622803, - "referencePoint": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - }, - "tanLambda": -1.99562406539917, - "time": 0.0, - }, - ], - "trackerHits": [ - { - "collectionID": 5, - "index": 0, - }, - { - "collectionID": 5, - "index": 1, - }, - ], - "tracks": [], - "type": 0, - }; - - loadMembers(object, data, members); - expect(object).toEqual({ - "id": 1, - "type": 0, - "chi2": 0.0, - "ndf": 0, - "dEdx": 0.0, - "dEdxError": 0.0, - "radiusOfInnermostHit": 17.0, - }); -}); - -test("load one to one relations with some definition and data", () => { - data = { - "charge": 1.0, - "clusters": [], - "covMatrix": [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - "energy": 12.062528610229492, - "goodnessOfPID": 0.0, - "mass": 2.315242290496826, - "momentum": { - "x": 11.738886833190918, - "y": 1.2114704847335815, - "z": 0.9354811906814575, - }, - "particleIDUsed": { - "collectionID": -2, - "index": -2, - }, - "particleIDs": [ - { - "collectionID": 4, - "index": 45, - }, - ], - "particles": [ - { - "collectionID": 14, - "index": 24, - }, - { - "collectionID": 14, - "index": 22, - }, - { - "collectionID": 14, - "index": 74, - }, - { - "collectionID": 14, - "index": 23, - }, - { - "collectionID": 14, - "index": 25, - }, - { - "collectionID": 14, - "index": 26, - }, - ], - "referencePoint": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - }, - "startVertex": { - "collectionID": 2, - "index": 2, - }, - "tracks": [], - "type": 0, - }; - const oneToOneRelations = [ - { - "type": "edm4hep::Vertex", - "name": "startVertex", - }, - ]; - const oneToOne = { - "startVertex": [], - }; - loadOneToOneRelations(object, data, oneToOneRelations, oneToOne, []); - expect(object.oneToOneRelations).not.toBeNull(); -}); - -test("load one to many relations with some definition and data", () => { - data = { - "PDG": -11, - "charge": 1.0, - "colorFlow": { - "a": 0, - "b": 0, - }, - "daughters": [ - { - "collectionID": 11, - "index": 4, - }, - { - "collectionID": 11, - "index": 5, - }, - ], - "endpoint": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - }, - "generatorStatus": 21, - "mass": 0.0, - "momentum": { - "x": 0.0, - "y": 0.0, - "z": -119.99999237060547, - }, - "momentumAtEndpoint": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - }, - "parents": [ - { - "collectionID": 11, - "index": 1, - }, - ], - "simulatorStatus": 0, - "spin": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - }, - "time": 0.0, - "vertex": { - "x": -0.01184066478163004, - "y": -2.074451003863942e-6, - "z": -0.08278788626194, - }, - }; - const oneToManyRelations = [ - { - "type": "edm4hep::MCParticle", - "name": "parents", - }, - { - "type": "edm4hep::MCParticle", - "name": "daughters", - }, - ]; - const oneToMany = { - "parents": [], - "daughters": [], - }; - loadOneToManyRelations(object, data, oneToManyRelations, oneToMany, []); - expect(object.oneToManyRelations.daughters.length).toEqual(2); - expect(object.oneToManyRelations.parents.length).toEqual(1); -}); diff --git a/test/filterMCParticle.test.js b/test/filterMCParticle.test.js index c3c54ca3..0b7989ea 100644 --- a/test/filterMCParticle.test.js +++ b/test/filterMCParticle.test.js @@ -5,20 +5,20 @@ import { Checkbox, buildCriteriaFunction, } from "../js/menu/filter/parameters.js"; -import { CheckboxBuilder } from "../js/menu/filter/builders.js"; let objects = {}; const data = { "Event 0": { "Collection": { + "collID": 0, "collType": "edm4hep::MCParticleCollection", "collection": [ { "momentum": 0, "charge": 0, "mass": 0, - "simStatus": 70, + "simulatorStatus": 70, "parents": [], "daughters": [ { @@ -31,7 +31,7 @@ const data = { "momentum": 100, "charge": 1, "mass": 10, - "simStatus": 24, + "simulatorStatus": 24, "daughters": [ { "collectionID": 0, @@ -49,7 +49,7 @@ const data = { "momentum": 200, "charge": 2, "mass": 20, - "simStatus": 25, + "simulatorStatus": 25, "daughters": [ { "collectionID": 0, @@ -67,7 +67,7 @@ const data = { "momentum": 300, "charge": 3, "mass": 30, - "simStatus": 26, + "simulatorStatus": 26, "daughters": [ { "collectionID": 0, @@ -85,7 +85,7 @@ const data = { "momentum": 400, "charge": 4, "mass": 40, - "simStatus": 27, + "simulatorStatus": 27, "parents": [ { "collectionID": 0, @@ -121,8 +121,8 @@ describe("filter by ranges", () => { const filteredObjects = reconnect(criteriaFunction, objects); expect( - filteredObjects["edm4hep::MCParticle"].collection.map( - (mcParticle) => mcParticle.id + filteredObjects.datatypes["edm4hep::MCParticle"].collection.map( + (mcParticle) => mcParticle.index ) ).toEqual([3, 4]); }); @@ -145,8 +145,8 @@ describe("filter by ranges", () => { const filteredObjects = reconnect(criteriaFunction, objects); expect( - filteredObjects["edm4hep::MCParticle"].collection.map( - (mcParticle) => mcParticle.id + filteredObjects.datatypes["edm4hep::MCParticle"].collection.map( + (mcParticle) => mcParticle.index ) ).toEqual([3, 4]); }); @@ -154,7 +154,7 @@ describe("filter by ranges", () => { describe("filter by checkboxes", () => { it("filter by a single checkbox", () => { - const simulatorStatus = new Checkbox("simStatus", 23); + const simulatorStatus = new Checkbox("simulatorStatus", 23); simulatorStatus.checked = true; const checkboxFilters = Checkbox.buildFilter([simulatorStatus]); const criteriaFunction = buildCriteriaFunction(checkboxFilters); @@ -162,18 +162,18 @@ describe("filter by checkboxes", () => { const filteredObjects = reconnect(criteriaFunction, objects); expect( - filteredObjects["edm4hep::MCParticle"].collection.map( - (mcParticle) => mcParticle.id + filteredObjects.datatypes["edm4hep::MCParticle"].collection.map( + (mcParticle) => mcParticle.index ) ).toEqual([]); }); it("filter by a combination of checkboxes", () => { - const simulatorStatus1 = new Checkbox("simStatus", 23); + const simulatorStatus1 = new Checkbox("simulatorStatus", 23); simulatorStatus1.checked = true; - const simulatorStatus2 = new Checkbox("simStatus", 26); + const simulatorStatus2 = new Checkbox("simulatorStatus", 26); simulatorStatus2.checked = true; - const simulatorStatus3 = new Checkbox("simStatus", 27); + const simulatorStatus3 = new Checkbox("simulatorStatus", 27); simulatorStatus3.checked = true; const checkboxFilters = Checkbox.buildFilter([ simulatorStatus1, @@ -185,10 +185,10 @@ describe("filter by checkboxes", () => { const filteredObjects = reconnect(criteriaFunction, objects); expect( - filteredObjects["edm4hep::MCParticle"].collection.map( - (mcParticle) => mcParticle.id + filteredObjects.datatypes["edm4hep::MCParticle"].collection.map( + (mcParticle) => mcParticle.index ) - ).toEqual([]); + ).toEqual([3, 4]); }); }); @@ -198,7 +198,7 @@ describe("filter by ranges and checkboxes", () => { property: "charge", unit: "e", }); - const simulatorStatus = new Checkbox("simStatus", 26); + const simulatorStatus = new Checkbox("simulatorStatus", 26); const rangeFilters = Range.buildFilter([charge]); const checkboxFilters = Checkbox.buildFilter([simulatorStatus]); const criteriaFunction = buildCriteriaFunction( @@ -209,8 +209,8 @@ describe("filter by ranges and checkboxes", () => { const filteredObjects = reconnect(criteriaFunction, objects); expect( - filteredObjects["edm4hep::MCParticle"].collection.map( - (mcParticle) => mcParticle.id + filteredObjects.datatypes["edm4hep::MCParticle"].collection.map( + (mcParticle) => mcParticle.index ) ).toEqual([0, 1, 2, 3, 4]); }); @@ -221,7 +221,7 @@ describe("filter by ranges and checkboxes", () => { unit: "e", }); charge.max = 3; - const simulatorStatus = new Checkbox("simStatus", 23); + const simulatorStatus = new Checkbox("simulatorStatus", 23); simulatorStatus.checked = true; const rangeFilters = Range.buildFilter([charge]); const checkboxFilters = Checkbox.buildFilter([simulatorStatus]); @@ -233,8 +233,8 @@ describe("filter by ranges and checkboxes", () => { const filteredObjects = reconnect(criteriaFunction, objects); expect( - filteredObjects["edm4hep::MCParticle"].collection.map( - (mcParticle) => mcParticle.id + filteredObjects.datatypes["edm4hep::MCParticle"].collection.map( + (mcParticle) => mcParticle.index ) ).toEqual([]); }); diff --git a/test/load.json b/test/load.json index daa5c4c7..9f99294d 100644 --- a/test/load.json +++ b/test/load.json @@ -178,7 +178,7 @@ ], "particles": [ { - "collectionID": 14, + "collectionID": 13, "index": 1 } ], diff --git a/test/load.test.js b/test/load.test.js index c9e813c4..2f81e54c 100644 --- a/test/load.test.js +++ b/test/load.test.js @@ -1,223 +1,51 @@ -import { loadObjectType, loadObjects } from "../js/types/load.js"; -import { datatypes } from "../output/datatypes.js"; -import { objectTypes } from "../js/types/objects.js"; +import { loadObjects } from "../js/types/load.js"; import json from "./load.json" assert { type: "json" }; -test("load a collection of particles", () => { - const type = "edm4hep::Track"; - const collection = [ - { - "chi2": 0.0, - "dEdx": 0.0, - "dEdxError": 0.0, - "dxQuantities": [ - { - "error": 0.0, - "type": 0, - "value": 0.0, - }, - ], - "ndf": 0, - "radiusOfInnermostHit": 17.0, - "subDetectorHitNumbers": [], - "trackStates": [ - { - "D0": -0.05963143706321716, - "Z0": -0.9309114217758179, - "covMatrix": [ - 0.00838407501578331, -0.0005293499561958015, 3.361300696269609e-5, - 1.1750994133308268e-7, -7.233749155233227e-9, 3.568003878462456e-11, - 0.00010174162162002176, -6.439207481889753e-6, 3.929798264579176e-9, - 0.004548509605228901, -1.0186887266172562e-5, 6.442872972911573e-7, - -3.0552818608420296e-10, -0.00028709869366139174, - 1.831963163567707e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - ], - "location": 0, - "omega": 0.0032351072877645493, - "phi": -2.237527847290039, - "referencePoint": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - }, - "tanLambda": -4.997164249420166, - "time": 0.0, - }, - ], - "trackerHits": [ - { - "collectionID": 5, - "index": 0, - }, - { - "collectionID": 5, - "index": 1, - }, - ], - "tracks": [], - "type": 0, - }, - { - "chi2": 0.0, - "dEdx": 0.0, - "dEdxError": 0.0, - "dxQuantities": [ - { - "error": 0.0, - "type": 0, - "value": 0.0, - }, - ], - "ndf": 0, - "radiusOfInnermostHit": 17.0, - "subDetectorHitNumbers": [], - "trackStates": [ - { - "D0": -0.0391334593296051, - "Z0": -0.9311737418174744, - "covMatrix": [ - 0.0035914022009819746, -0.00022589370200876147, - 1.4322121387522202e-5, 4.24616963812241e-8, -2.698678391865883e-9, - 8.189490210107342e-13, 8.363036613445729e-5, -5.291213710734155e-6, - 3.081625399303789e-9, 0.00030561615130864084, -6.513672360597411e-6, - 4.117676155601657e-7, -2.10651204812784e-10, -1.7557771570864134e-5, - 1.1170427569595631e-6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - ], - "location": 0, - "omega": -0.0019104206003248692, - "phi": -2.798056125640869, - "referencePoint": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - }, - "tanLambda": -4.306049823760986, - "time": 0.0, - }, - ], - "trackerHits": [ - { - "collectionID": 5, - "index": 2, - }, - { - "collectionID": 5, - "index": 3, - }, - ], - "tracks": [], - "type": 0, - }, - { - "chi2": 0.0, - "dEdx": 0.0, - "dEdxError": 0.0, - "dxQuantities": [ - { - "error": 0.0, - "type": 0, - "value": 0.0, - }, - ], - "ndf": 0, - "radiusOfInnermostHit": 17.0, - "subDetectorHitNumbers": [], - "trackStates": [ - { - "D0": -0.050047606229782104, - "Z0": -0.9527733325958252, - "covMatrix": [ - 0.007592398207634687, -0.0004792569379787892, 3.0433289794018492e-5, - 1.0142189665884871e-7, -6.283491504888161e-9, - 2.5339810111324468e-11, 0.0001311719825025648, - -8.306328709295485e-6, 4.581083068444514e-9, 0.0035560736432671547, - -1.2910893929074518e-5, 8.168165095412405e-7, - -3.557094618855672e-10, -0.00022385688498616219, - 1.4287375051935669e-5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, - ], - "location": 0, - "omega": -0.003163003595545888, - "phi": -2.4250643253326416, - "referencePoint": { - "x": 0.0, - "y": 0.0, - "z": 0.0, - }, - "tanLambda": -3.454428195953369, - "time": 0.0, - }, - ], - "trackerHits": [ - { - "collectionID": 5, - "index": 4, - }, - { - "collectionID": 5, - "index": 5, - }, - ], - "tracks": [], - "type": 0, - }, - ]; - const [objects, oneToOne, { trackerHits, tracks }] = loadObjectType( - collection, - datatypes[type], - objectTypes[type] - ); - - expect(objects.length).toEqual(3); - expect(oneToOne).toEqual({}); - expect(trackerHits.length).toEqual(6); - expect(tracks.length).toEqual(0); -}); - test("load a json file with a collection of objects", () => { const objects = loadObjects(json, 1, [ "edm4hep::MCParticle", "edm4hep::ReconstructedParticle", ]); - expect(objects["edm4hep::MCParticle"]).toBeDefined(); - expect(objects["edm4hep::ReconstructedParticle"]).toBeDefined(); + const datatypes = objects.datatypes; + + expect(datatypes["edm4hep::MCParticle"]).toBeDefined(); + expect(datatypes["edm4hep::ReconstructedParticle"]).toBeDefined(); - expect(objects["edm4hep::MCParticle"].collection.length).toEqual(3); + expect(datatypes["edm4hep::MCParticle"].collection.length).toEqual(3); expect( - objects["edm4hep::MCParticle"].collection.map((val) => val.id) + datatypes["edm4hep::MCParticle"].collection.map((val) => val.index) ).toEqual([0, 1, 2]); - expect(objects["edm4hep::MCParticle"].oneToMany["daughters"]).toBeDefined(); - expect(objects["edm4hep::MCParticle"].oneToMany["parents"]).toBeDefined(); + expect(datatypes["edm4hep::MCParticle"].oneToMany["daughters"]).toBeDefined(); + expect(datatypes["edm4hep::MCParticle"].oneToMany["parents"]).toBeDefined(); + expect( - objects["edm4hep::MCParticle"].oneToMany["daughters"][0].from.id + datatypes["edm4hep::MCParticle"].oneToMany["daughters"][0].from.index ).toEqual(0); expect( - objects["edm4hep::MCParticle"].oneToMany["daughters"][0].to.id + datatypes["edm4hep::MCParticle"].oneToMany["daughters"][0].to.index ).toEqual(2); - expect(objects["edm4hep::ReconstructedParticle"].collection.length).toEqual( + expect(datatypes["edm4hep::ReconstructedParticle"].collection.length).toEqual( 2 ); expect( - objects["edm4hep::ReconstructedParticle"].collection.map((val) => val.id) + datatypes["edm4hep::ReconstructedParticle"].collection.map( + (val) => val.index + ) ).toEqual([0, 1]); expect( - objects["edm4hep::ReconstructedParticle"].oneToMany["particles"] + datatypes["edm4hep::ReconstructedParticle"].oneToMany["particles"] ).toBeDefined(); expect( - objects["edm4hep::ReconstructedParticle"].oneToOne["startVertex"] + datatypes["edm4hep::ReconstructedParticle"].oneToOne["startVertex"] ).toBeDefined(); expect( - objects["edm4hep::ReconstructedParticle"].oneToMany["particles"][0].from.id + datatypes["edm4hep::ReconstructedParticle"].oneToMany["particles"][0].from + .index ).toEqual(0); expect( - objects["edm4hep::ReconstructedParticle"].oneToMany["particles"][0].to.id + datatypes["edm4hep::ReconstructedParticle"].oneToMany["particles"][0].to + .index ).toEqual(1); - - expect( - objects["edm4hep::ReconstructedParticle"].oneToOne["startVertex"][0].to - ).toBeUndefined(); - expect( - objects["edm4hep::ReconstructedParticle"].oneToOne["startVertex"][1].to - ).toBeUndefined(); }); diff --git a/test/objects.test.js b/test/objects.test.js index b52328dd..1c483342 100644 --- a/test/objects.test.js +++ b/test/objects.test.js @@ -6,6 +6,8 @@ describe("MCParticle", () => { beforeEach(() => { mcParticle = new MCParticle(1); + mcParticle.x = 0; + mcParticle.y = 0; }); afterEach(() => { @@ -89,7 +91,11 @@ describe("Link", () => { beforeEach(() => { firstObject = new MCParticle(0); + firstObject.x = 0; + firstObject.y = 0; secondObject = new MCParticle(1); + secondObject.x = 0; + secondObject.y = 0; link = new Link(firstObject, secondObject); }); From b030b93ca133708ec65574afeba338454db175d9 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 25 Jun 2024 22:01:24 -0500 Subject: [PATCH 09/21] give enough space on limits --- js/graph/random-positions.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/js/graph/random-positions.js b/js/graph/random-positions.js index d2aecdc5..6f541cff 100644 --- a/js/graph/random-positions.js +++ b/js/graph/random-positions.js @@ -1,5 +1,8 @@ import { canvas } from "../main.js"; +const boxWidth = 120; +const boxHeight = 240; + function randomNumber(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } @@ -10,8 +13,8 @@ export function generateRandomPositions(nodes) { for (const node of nodes) { if (isNaN(node.x) && isNaN(node.y)) { - node.x = randomNumber(0, width); - node.y = randomNumber(0, height); + node.x = randomNumber(0, width - boxWidth); + node.y = randomNumber(0, height - boxHeight); } } } From 6022e7d56934d6139575b627f65482cde92e5501 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Thu, 27 Jun 2024 18:15:49 -0500 Subject: [PATCH 10/21] visualize recoparticles in a tree like structure --- js/menu/event-number.js | 7 ----- js/place-objects.js | 2 -- js/types/links.js | 4 +-- js/types/objects.js | 69 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/js/menu/event-number.js b/js/menu/event-number.js index fd89423c..82c4b3fc 100644 --- a/js/menu/event-number.js +++ b/js/menu/event-number.js @@ -72,13 +72,6 @@ function loadSelectedEvent() { classType.setup(collection, canvas); } - // Prepare objects for drawing - // if (!layoutObjects[currentEvent]) { - const nodes = placeObjects(currentObjects); - applyNewPositions(currentObjects, nodes); - // layoutObjects[currentEvent] = true; - // } - drawAll(ctx, currentObjects); getVisible(currentObjects, visibleObjects); diff --git a/js/place-objects.js b/js/place-objects.js index d1880aa0..ee132ec8 100644 --- a/js/place-objects.js +++ b/js/place-objects.js @@ -60,8 +60,6 @@ export function placeObjects(objects) { generateRandomPositions(nodes); - console.log(nodes, edges); - return nodes; } diff --git a/js/types/links.js b/js/types/links.js index be057816..60ed7b14 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -2,7 +2,7 @@ export const colors = { "daughters": "#00AA00", "parents": "#AA0000", "mcreco": "#0000AA", - "clusters": "#AA00AA", + "particles": "#AA00AA", }; export function generateRandomColor() { @@ -140,7 +140,7 @@ export class MCRecoParticleAssociation extends Link { export class Particles extends Link { constructor(from, to) { super(from, to); - this.color = colors["clusters"]; + this.color = colors["particles"]; } } diff --git a/js/types/objects.js b/js/types/objects.js index 5dba612e..5c0599ec 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -56,7 +56,74 @@ export class ReconstructedParticle extends EDMObject { drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); } - static setup() {} + static setup(recoCollection, canvas) { + 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; + recoParticle.y = i * verticalGap + i * boxHeight; + }); + }); + } static filter() {} } From 322759f0387a98c340d8349434843b1d21094dc6 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 29 Jun 2024 21:16:54 -0500 Subject: [PATCH 11/21] build 3 views for visualizations --- css/views.css | 41 +++++++++ index.html | 11 ++- js/draw.js | 4 +- js/event-number.js | 97 +++++++++++++++++++++ js/events.js | 7 +- js/graph/fruchrein.js | 4 - js/graph/random-positions.js | 20 ----- js/lib/copy.js | 24 ++--- js/main.js | 99 ++++++++++----------- js/menu/event-number.js | 125 -------------------------- js/menu/filter/filter.js | 4 +- js/place-objects.js | 75 ---------------- js/types/links.js | 20 +++++ js/types/objects.js | 160 +--------------------------------- js/views/mcparticletree.js | 103 ++++++++++++++++++++++ js/views/mcrecoassociation.js | 74 ++++++++++++++++ js/views/recoparticletree.js | 90 +++++++++++++++++++ js/views/views-dictionary.js | 39 +++++++++ js/views/views.js | 125 ++++++++++++++++++++++++++ 19 files changed, 667 insertions(+), 455 deletions(-) create mode 100644 css/views.css create mode 100644 js/event-number.js delete mode 100644 js/graph/fruchrein.js delete mode 100644 js/graph/random-positions.js delete mode 100644 js/menu/event-number.js delete mode 100644 js/place-objects.js create mode 100644 js/views/mcparticletree.js create mode 100644 js/views/mcrecoassociation.js create mode 100644 js/views/recoparticletree.js create mode 100644 js/views/views-dictionary.js create mode 100644 js/views/views.js diff --git a/css/views.css b/css/views.css new file mode 100644 index 00000000..f6ebf157 --- /dev/null +++ b/css/views.css @@ -0,0 +1,41 @@ +#available-views { + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: flex-start; + align-items: center; + padding: 8px; +} + +.view-button { + background-color: #f1f1f1; + border: 1px solid #000; + border-radius: 5px; + padding: 8px; + margin: 8px; + cursor: pointer; + height: fit-content; +} + +#views { + display: none; + flex-direction: column; + position: fixed; + top: 25%; + left: 10px; + width: fit-content; + height: 50%; + background-color: #e1e1e1; + padding: 15px; + border: 1px solid #000; + border-radius: 5px; +} + +#view-selector { + display: flex; + flex-direction: column; + justify-content: flex-start; + overflow-y: auto; + overflow-x: hidden; + width: fit-content; +} diff --git a/index.html b/index.html index c9dec4db..96c0dcce 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,7 @@ + @@ -41,6 +42,8 @@ +
+

@@ -147,12 +150,18 @@
+
+

Select a view:

+
+
+ - + + \ No newline at end of file diff --git a/js/draw.js b/js/draw.js index b82a7b4f..e5c31903 100644 --- a/js/draw.js +++ b/js/draw.js @@ -10,7 +10,7 @@ function draw(objects) { } } - for (const elements of Object.values(datatypes ?? {})) { + for (const elements of Object.values(datatypes)) { const { collection, oneToMany, oneToOne } = elements; for (const links of Object.values(oneToMany)) { @@ -31,7 +31,7 @@ function draw(objects) { } } -export function drawAll(ctx, loadedObjects) { +export function drawAll(loadedObjects) { ctx.clearRect(0, 0, canvas.width, canvas.height); draw(loadedObjects); diff --git a/js/event-number.js b/js/event-number.js new file mode 100644 index 00000000..e752f71e --- /dev/null +++ b/js/event-number.js @@ -0,0 +1,97 @@ +import { loadObjects } from "./types/load.js"; +import { copyObject } from "./lib/copy.js"; +import { canvas, jsonData, selectedObjectTypes } from "./main.js"; +import { objectTypes } from "./types/objects.js"; +import { drawCurrentView, saveScrollLocation } from "./views/views.js"; +// import { +// bits, +// genStatus, +// renderRangeParameters, +// parametersRange, +// renderGenSim, +// } from "./menu/filter/filter.js"; + +// const filters = document.getElementById("filters"); +// const manipulationTools = document.getElementsByClassName("manipulation-tool"); +const eventNumber = document.getElementById("selected-event"); +const previousEvent = document.getElementById("previous-event"); +const nextEvent = document.getElementById("next-event"); + +const currentEvent = {}; + +const eventCollection = {}; +const currentObjects = {}; + +function updateEventNumber() { + if (eventNumber.firstChild) { + eventNumber.removeChild(eventNumber.firstChild); + } + eventNumber.appendChild( + document.createTextNode(`Event: ${currentEvent.event}`) + ); +} + +function loadSelectedEvent() { + if (eventCollection[currentEvent.event] === undefined) { + const objects = loadObjects( + jsonData.data, + currentEvent.event, + selectedObjectTypes.types + ); + + eventCollection[currentEvent.event] = objects; + + for (const [key, value] of Object.entries( + eventCollection[currentEvent.event].datatypes + )) { + const classType = objectTypes[key]; + const collection = value.collection; + classType.setup(collection, canvas); + } + copyObject(objects, currentObjects); + } else { + copyObject(eventCollection[currentEvent.event], currentObjects); + } + + // --> menu/filtering stuff + // for (const tool of manipulationTools) { + // tool.style.display = "flex"; + // } + // const mcObjects = loadedObjects.datatypes["edm4hep::MCParticle"].collection; + // genStatus.reset(); + // mcObjects.forEach((mcObject) => { + // genStatus.add(mcObject.generatorStatus); + // }); + // genStatus.setCheckBoxes(); + // filters.replaceChildren(); + + // renderRangeParameters(parametersRange); + // renderGenSim(bits, genStatus); +} + +export function renderEvent(eventNumber) { + saveScrollLocation(); + currentEvent.event = eventNumber; + loadSelectedEvent(); + updateEventNumber(); + drawCurrentView(); +} + +previousEvent.addEventListener("click", () => { + const newEventNum = `${parseInt(currentEvent.event) - 1}`; + renderEvent(newEventNum); +}); +nextEvent.addEventListener("click", () => { + const newEventNum = `${parseInt(currentEvent.event) + 1}`; + renderEvent(newEventNum); +}); +eventNumber.addEventListener("click", () => { + const eventSelectorMenu = document.getElementById("event-selector-menu"); + if (eventSelectorMenu.style.display === "flex") { + eventSelectorMenu.style.display = "none"; + } else { + eventSelectorMenu.style.display = "flex"; + } +}); + +export { currentObjects, currentEvent }; diff --git a/js/events.js b/js/events.js index 862a3d1a..9c6a0043 100644 --- a/js/events.js +++ b/js/events.js @@ -1,4 +1,4 @@ -import { canvas, ctx } from "./main.js"; +import { canvas } from "./main.js"; import { drawAll, drawVisible } from "./draw.js"; const mouseDown = function (event, visibleObjects, dragTools) { @@ -30,7 +30,7 @@ const mouseUp = function (event, currentObjects, dragTools) { dragTools.isDragging = false; // console.time("drawAll"); - drawAll(ctx, currentObjects); + drawAll(currentObjects); // console.timeEnd("drawAll"); }; @@ -47,7 +47,6 @@ const mouseMove = function (event, visibleObjects, dragTools) { if (!dragTools.isDragging) { return; } - event.preventDefault(); const boundigClientRect = canvas.getBoundingClientRect(); const mouseX = parseInt(event.clientX - boundigClientRect.x); @@ -60,9 +59,7 @@ const mouseMove = function (event, visibleObjects, dragTools) { draggedObject.x += dx; draggedObject.y += dy; - // console.time("drawVisible"); drawVisible(visibleObjects); - // console.timeEnd("drawVisible"); dragTools.prevMouseX = mouseX; dragTools.prevMouseY = mouseY; diff --git a/js/graph/fruchrein.js b/js/graph/fruchrein.js deleted file mode 100644 index 8a7334ee..00000000 --- a/js/graph/fruchrein.js +++ /dev/null @@ -1,4 +0,0 @@ -const nodeHeight = 240; -const nodeWidth = 120; - -export function fruchtermanReingold(nodes, edges) {} diff --git a/js/graph/random-positions.js b/js/graph/random-positions.js deleted file mode 100644 index 6f541cff..00000000 --- a/js/graph/random-positions.js +++ /dev/null @@ -1,20 +0,0 @@ -import { canvas } from "../main.js"; - -const boxWidth = 120; -const boxHeight = 240; - -function randomNumber(min, max) { - return Math.floor(Math.random() * (max - min + 1) + min); -} - -export function generateRandomPositions(nodes) { - const width = canvas.width; - const height = canvas.height; - - for (const node of nodes) { - if (isNaN(node.x) && isNaN(node.y)) { - node.x = randomNumber(0, width - boxWidth); - node.y = randomNumber(0, height - boxHeight); - } - } -} diff --git a/js/lib/copy.js b/js/lib/copy.js index 9cf919b7..c650058c 100644 --- a/js/lib/copy.js +++ b/js/lib/copy.js @@ -1,33 +1,33 @@ -export function copyObject(objToCopy, updatedObject) { - for (const [key, value] of Object.entries(objToCopy)) { - updatedObject[key] = value; +export function copyObject(source, destiny) { + for (const [key, value] of Object.entries(source)) { + destiny[key] = value; } } -export function emptyCopyObject(objToCopy, updatedObject) { - updatedObject.datatypes = {}; +export function emptyCopyObject(source, destiny) { + destiny.datatypes = {}; - for (const [objectType, elements] of Object.entries(objToCopy.datatypes)) { + for (const [objectType, elements] of Object.entries(source.datatypes)) { const { _, oneToMany, oneToOne } = elements; - updatedObject.datatypes[objectType] = { + destiny.datatypes[objectType] = { collection: [], oneToMany: {}, oneToOne: {}, }; for (const name in oneToMany) { - updatedObject.datatypes[objectType].oneToMany[name] = []; + destiny.datatypes[objectType].oneToMany[name] = []; } for (const name in oneToOne) { - updatedObject.datatypes[objectType].oneToOne[name] = []; + destiny.datatypes[objectType].oneToOne[name] = []; } } - updatedObject.associations = {}; + destiny.associations = {}; - for (const key in objToCopy.associations) { - updatedObject.associations[key] = []; + for (const key in source.associations) { + destiny.associations[key] = []; } } diff --git a/js/main.js b/js/main.js index 1bf492be..39a3f759 100644 --- a/js/main.js +++ b/js/main.js @@ -1,9 +1,9 @@ import { errorMsg } from "./tools.js"; -import { PdgToggle } from "./menu/show-pdg.js"; -import { drawAll } from "./draw.js"; -import { getWidthFilterContent } from "./menu/filter/filter.js"; -import { mouseDown, mouseUp, mouseOut, mouseMove, onScroll } from "./events.js"; -import { renderEvent } from "./menu/event-number.js"; +import { renderEvent } from "./event-number.js"; +import { setView, getView } from "./views/views.js"; +import { views } from "./views/views-dictionary.js"; +// import { PdgToggle } from "./menu/show-pdg.js"; +// import { getWidthFilterContent } from "./menu/filter/filter.js"; const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); @@ -13,19 +13,6 @@ canvas.height = window.innerHeight; const jsonData = {}; -const dragTools = { - draggedObject: null, - isDragging: false, - prevMouseX: 0, - prevMouseY: 0, -}; - -const loadedObjects = {}; - -const currentObjects = {}; - -const visibleObjects = {}; - const selectedObjectTypes = { types: [ "edm4hep::MCParticle", @@ -34,22 +21,6 @@ const selectedObjectTypes = { ], }; -canvas.onmousedown = (event) => { - mouseDown(event, visibleObjects, dragTools); -}; -canvas.onmouseup = (event) => { - mouseUp(event, currentObjects, dragTools); -}; -canvas.onmouseout = (event) => { - mouseOut(event, dragTools); -}; -canvas.onmousemove = (event) => { - mouseMove(event, visibleObjects, dragTools); -}; -window.onscroll = () => { - onScroll(currentObjects, visibleObjects); -}; - function hideInputModal() { const modal = document.getElementById("input-modal"); @@ -58,9 +29,16 @@ function hideInputModal() { function showEventSwitcher() { const eventSwitcher = document.getElementById("event-switcher"); + eventSwitcher.style.display = "flex"; } +function showViewsMenu() { + const viewsMenu = document.getElementById("views"); + + viewsMenu.style.display = "flex"; +} + document.getElementById("input-file").addEventListener("change", (event) => { for (const file of event.target.files) { if (!file.name.endsWith("edm4hep.json")) { @@ -105,6 +83,27 @@ document.getElementById("input-file").addEventListener("change", (event) => { eventSelectorMenu.style.display = "none"; }); }); + + const availableViews = document.getElementById("available-views"); + availableViews.replaceChildren(); + const buttons = []; + for (const key in views) { + const button = document.createElement("button"); + button.appendChild(document.createTextNode(key)); + button.className = "view-button"; + button.onclick = (event) => { + event.preventDefault(); + setView(key); + for (const otherButton of buttons) { + if (otherButton !== button) { + otherButton.style.backgroundColor = "#f1f1f1"; + } + } + button.style.backgroundColor = "#c5c5c5"; + }; + buttons.push(button); + availableViews.appendChild(button); + } }); reader.readAsText(file); break; @@ -121,28 +120,26 @@ document return; } + if (getView() === undefined) { + errorMsg("No view selected!"); + return; + } + const eventNum = document.getElementById("event-number").value; hideInputModal(); showEventSwitcher(); + showViewsMenu(); renderEvent(eventNum); - const width = getWidthFilterContent(); - filter.style.width = width; - const pdgToggle = new PdgToggle("show-pdg"); - pdgToggle.init(() => { - pdgToggle.toggle(currentObjects, () => { - drawAll(ctx, currentObjects); - }); - }); + // const width = getWidthFilterContent(); + // filter.style.width = width; + // const pdgToggle = new PdgToggle("show-pdg"); + // pdgToggle.init(() => { + // pdgToggle.toggle(currentObjects, () => { + // drawAll(currentObjects); + // }); + // }); }); -export { - canvas, - ctx, - loadedObjects, - currentObjects, - visibleObjects, - jsonData, - selectedObjectTypes, -}; +export { canvas, ctx, jsonData, selectedObjectTypes }; diff --git a/js/menu/event-number.js b/js/menu/event-number.js deleted file mode 100644 index 82c4b3fc..00000000 --- a/js/menu/event-number.js +++ /dev/null @@ -1,125 +0,0 @@ -import { loadObjects } from "../types/load.js"; -import { copyObject } from "../lib/copy.js"; -import { - loadedObjects, - currentObjects, - visibleObjects, - canvas, - ctx, -} from "../main.js"; -import { getVisible } from "../events.js"; -import { - bits, - genStatus, - renderRangeParameters, - parametersRange, - renderGenSim, -} from "./filter/filter.js"; -import { drawAll } from "../draw.js"; -import { objectTypes } from "../types/objects.js"; -import { jsonData, selectedObjectTypes } from "../main.js"; -import { placeObjects, applyNewPositions } from "../place-objects.js"; - -const filters = document.getElementById("filters"); -const eventNumber = document.getElementById("selected-event"); -const previousEvent = document.getElementById("previous-event"); -const nextEvent = document.getElementById("next-event"); -const manipulationTools = document.getElementsByClassName("manipulation-tool"); - -let currentEvent; - -const scrollLocation = {}; - -const layoutObjects = []; - -function updateEventNumber() { - if (eventNumber.firstChild) { - eventNumber.removeChild(eventNumber.firstChild); - } - eventNumber.appendChild(document.createTextNode(`Event: ${currentEvent}`)); -} - -function saveScrollLocation() { - if (scrollLocation[currentEvent] === undefined) return; - scrollLocation[currentEvent] = { - x: window.scrollX, - y: window.scrollY, - }; -} - -function loadSelectedEvent() { - const objects = loadObjects( - jsonData.data, - currentEvent, - selectedObjectTypes.types - ); - - copyObject(objects, loadedObjects); - copyObject(objects, currentObjects); - - const length = Object.values(loadedObjects.datatypes) - .map((obj) => obj.collection.length) - .reduce((a, b) => a + b, 0); - - if (length === 0) { - errorMsg("Event does not contain any objects!"); - return; - } - - for (const [key, value] of Object.entries(currentObjects.datatypes)) { - const classType = objectTypes[key]; - const collection = value.collection; - classType.setup(collection, canvas); - } - - drawAll(ctx, currentObjects); - getVisible(currentObjects, visibleObjects); - - if (scrollLocation[currentEvent] === undefined) { - scrollLocation[currentEvent] = { - x: (canvas.width - window.innerWidth) / 2, - y: 0, - }; - } - - window.scroll(scrollLocation[currentEvent].x, scrollLocation[currentEvent].y); - - // menu/filtering stuff - for (const tool of manipulationTools) { - tool.style.display = "flex"; - } - const mcObjects = loadedObjects.datatypes["edm4hep::MCParticle"].collection; - genStatus.reset(); - mcObjects.forEach((mcObject) => { - genStatus.add(mcObject.generatorStatus); - }); - genStatus.setCheckBoxes(); - filters.replaceChildren(); - - renderRangeParameters(parametersRange); - renderGenSim(bits, genStatus); -} - -export function renderEvent(eventNumber) { - saveScrollLocation(); - currentEvent = eventNumber; - loadSelectedEvent(); - updateEventNumber(); -} - -previousEvent.addEventListener("click", () => { - const newEventNum = `${parseInt(currentEvent) - 1}`; - renderEvent(newEventNum); -}); -nextEvent.addEventListener("click", () => { - const newEventNum = `${parseInt(currentEvent) + 1}`; - renderEvent(newEventNum); -}); -eventNumber.addEventListener("click", () => { - const eventSelectorMenu = document.getElementById("event-selector-menu"); - if (eventSelectorMenu.style.display === "flex") { - eventSelectorMenu.style.display = "none"; - } else { - eventSelectorMenu.style.display = "flex"; - } -}); diff --git a/js/menu/filter/filter.js b/js/menu/filter/filter.js index a26e577c..a764dd09 100644 --- a/js/menu/filter/filter.js +++ b/js/menu/filter/filter.js @@ -115,7 +115,7 @@ function applyFilter(loadedObjects, currentObjects, visibleObjects) { copyObject(filteredObjects, currentObjects); - drawAll(ctx, currentObjects); + drawAll(currentObjects); getVisible(currentObjects, visibleObjects); } @@ -123,7 +123,7 @@ function applyFilter(loadedObjects, currentObjects, visibleObjects) { function removeFilter(loadedObjects, currentObjects, visibleObjects) { copyObject(loadedObjects, currentObjects); - drawAll(ctx, currentObjects); + drawAll(currentObjects); getVisible(currentObjects, visibleObjects); diff --git a/js/place-objects.js b/js/place-objects.js deleted file mode 100644 index ee132ec8..00000000 --- a/js/place-objects.js +++ /dev/null @@ -1,75 +0,0 @@ -import { fruchtermanReingold } from "./graph/fruchrein.js"; -import { generateRandomPositions } from "./graph/random-positions.js"; - -function objectToNode(object) { - const edges = []; - - const oneToManyRelations = object.oneToManyRelations ?? {}; - const oneToOneRelations = object.oneToOneRelations ?? {}; - const associations = object.associations ?? {}; - - for (const link of Object.values(oneToOneRelations)) { - edges.push(link); - } - - for (const link of Object.values(associations)) { - edges.push(link); - } - - for (const links of Object.values(oneToManyRelations)) { - for (const link of links) { - edges.push(link); - } - } - - return { - x: object.x, - y: object.y, - edges, - }; -} - -export function placeObjects(objects) { - const nodes = []; - const edges = []; - - const datatypes = objects.datatypes; - const associations = objects.associations; - - for (const { collection, oneToOne, oneToMany } of Object.values(datatypes)) { - for (const object of collection) { - nodes.push(objectToNode(object)); - } - for (const links of Object.values(oneToOne)) { - for (const link of links) { - edges.push(link); - } - } - for (const links of Object.values(oneToMany)) { - for (const link of links) { - edges.push(link); - } - } - } - - for (const collection of Object.values(associations)) { - for (const association of collection) { - edges.push(association); - } - } - - generateRandomPositions(nodes); - - return nodes; -} - -export function applyNewPositions(objects, nodes) { - let index = 0; - Object.values(objects.datatypes).forEach(({ collection }) => { - collection.forEach((obj) => { - obj.x = nodes[index].x; - obj.y = nodes[index].y; - index++; - }); - }); -} diff --git a/js/types/links.js b/js/types/links.js index 60ed7b14..1d83013d 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -135,6 +135,26 @@ export class MCRecoParticleAssociation extends Link { this.color = colors["mcreco"]; this.weight = weight; } + + draw(ctx) { + const boxFrom = this.from; + const boxTo = this.to; + + const fromX = boxFrom.x + boxFrom.width / 2; + const fromY = boxFrom.y + boxFrom.height / 2; + + const toX = boxTo.x + boxTo.width / 2; + const toY = boxTo.y + boxTo.height / 2; + + ctx.save(); + ctx.strokeStyle = this.color; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(fromX, fromY); + ctx.lineTo(toX, toY); + ctx.stroke(); + ctx.restore(); + } } export class Particles extends Link { diff --git a/js/types/objects.js b/js/types/objects.js index 5c0599ec..8c2f6bef 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -56,74 +56,7 @@ export class ReconstructedParticle extends EDMObject { drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); } - static setup(recoCollection, canvas) { - 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; - recoParticle.y = i * verticalGap + i * boxHeight; - }); - }); - } + static setup(recoCollection) {} static filter() {} } @@ -235,7 +168,7 @@ export class MCParticle extends EDMObject { ctx.restore(); } - static setup(mcCollection, canvas) { + static setup(mcCollection) { for (const mcParticle of mcCollection) { const parentLength = mcParticle.oneToManyRelations["parents"].length; const daughterLength = mcParticle.oneToManyRelations["daughters"].length; @@ -268,95 +201,6 @@ export class MCParticle extends EDMObject { mcParticle.time = Math.round(mcParticle.time * 100) / 100; mcParticle.mass = Math.round(mcParticle.mass * 100) / 100; } - - const getMaxRow = (parentLinks) => { - let maxRow = -1; - for (const parentLink of parentLinks) { - const parent = parentLink.from; - if (parent.row === -1) { - return -1; - } - - if (parent.row > maxRow) { - maxRow = parent.row; - } - } - - return maxRow; - }; - - let repeat = true; - while (repeat) { - repeat = false; - for (const mcParticle of mcCollection) { - if (mcParticle.row >= 0) { - continue; - } - const parentRow = getMaxRow(mcParticle.oneToManyRelations["parents"]); - if (parentRow >= 0) { - mcParticle.row = parentRow + 1; - } else { - repeat = true; - } - } - } - - const rows = mcCollection.map((obj) => { - return obj.row; - }); - const maxRow = Math.max(...rows); - - // Order infoBoxes into rows - const mcRows = []; - for (let i = 0; i <= maxRow; i++) { - mcRows.push([]); - } - for (const box of mcCollection) { - mcRows[box.row].push(box); - } - const rowWidths = mcRows.map((obj) => { - return obj.length; - }); - const maxRowWidth = Math.max(...rowWidths); - - const boxWidth = mcCollection[0].width; - const boxHeight = mcCollection[0].height; - const horizontalGap = boxWidth * 0.4; - const verticalGap = boxHeight * 0.3; - - canvas.width = - boxWidth * (maxRowWidth + 1) + horizontalGap * (maxRowWidth + 1); - canvas.height = boxHeight * (maxRow + 1) + verticalGap * (maxRow + 2); - - for (const [i, row] of mcRows.entries()) { - for (const [j, box] of row.entries()) { - if (row.length % 2 === 0) { - const distanceFromCenter = j - row.length / 2; - if (distanceFromCenter < 0) { - box.x = - canvas.width / 2 - - boxWidth - - horizontalGap / 2 + - (distanceFromCenter + 1) * boxWidth + - (distanceFromCenter + 1) * horizontalGap; - } else { - box.x = - canvas.width / 2 + - horizontalGap / 2 + - distanceFromCenter * boxWidth + - distanceFromCenter * horizontalGap; - } - } else { - const distanceFromCenter = j - row.length / 2; - box.x = - canvas.width / 2 - - boxWidth / 2 + - distanceFromCenter * boxWidth + - distanceFromCenter * horizontalGap; - } - box.y = i * verticalGap + verticalGap + i * boxHeight; - } - } } static filter({ collection }, filteredObjects, criteriaFunction) { diff --git a/js/views/mcparticletree.js b/js/views/mcparticletree.js new file mode 100644 index 00000000..43d8e73d --- /dev/null +++ b/js/views/mcparticletree.js @@ -0,0 +1,103 @@ +import { canvas } from "../main.js"; + +export function mcParticleTree(viewCurrentObjects) { + const mcCollection = + viewCurrentObjects.datatypes["edm4hep::MCParticle"].collection ?? []; + + if (mcCollection.length === 0) { + alert("No MCParticles found in this event."); + } + + const getMaxRow = (parentLinks) => { + let maxRow = -1; + for (const parentLink of parentLinks) { + const parent = parentLink.from; + if (parent.row === -1) { + return -1; + } + + if (parent.row > maxRow) { + maxRow = parent.row; + } + } + + return maxRow; + }; + + let repeat = true; + while (repeat) { + repeat = false; + for (const mcParticle of mcCollection) { + if (mcParticle.row >= 0) { + continue; + } + const parentRow = getMaxRow(mcParticle.oneToManyRelations["parents"]); + if (parentRow >= 0) { + mcParticle.row = parentRow + 1; + } else { + repeat = true; + } + } + } + + const rows = mcCollection.map((obj) => { + return obj.row; + }); + const maxRow = Math.max(...rows); + + // Order infoBoxes into rows + const mcRows = []; + for (let i = 0; i <= maxRow; i++) { + mcRows.push([]); + } + for (const box of mcCollection) { + mcRows[box.row].push(box); + } + const rowWidths = mcRows.map((obj) => { + return obj.length; + }); + const maxRowWidth = Math.max(...rowWidths); + + const boxWidth = mcCollection[0].width; + const boxHeight = mcCollection[0].height; + const horizontalGap = boxWidth * 0.4; + const verticalGap = boxHeight * 0.3; + + canvas.width = + boxWidth * (maxRowWidth + 1) + horizontalGap * (maxRowWidth + 1); + canvas.height = boxHeight * (maxRow + 1) + verticalGap * (maxRow + 2); + + for (const [i, row] of mcRows.entries()) { + for (const [j, box] of row.entries()) { + if (row.length % 2 === 0) { + const distanceFromCenter = j - row.length / 2; + if (distanceFromCenter < 0) { + box.x = + canvas.width / 2 - + boxWidth - + horizontalGap / 2 + + (distanceFromCenter + 1) * boxWidth + + (distanceFromCenter + 1) * horizontalGap; + } else { + box.x = + canvas.width / 2 + + horizontalGap / 2 + + distanceFromCenter * boxWidth + + distanceFromCenter * horizontalGap; + } + } else { + const distanceFromCenter = j - row.length / 2; + box.x = + canvas.width / 2 - + boxWidth / 2 + + distanceFromCenter * boxWidth + + distanceFromCenter * horizontalGap; + } + box.y = i * verticalGap + verticalGap + i * boxHeight; + } + } +} + +export function mcParticleTreeScroll() { + return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; +} diff --git a/js/views/mcrecoassociation.js b/js/views/mcrecoassociation.js new file mode 100644 index 00000000..e8f19fb1 --- /dev/null +++ b/js/views/mcrecoassociation.js @@ -0,0 +1,74 @@ +import { canvas } from "../main.js"; +import { emptyCopyObject } from "../lib/copy.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; + } +} + +export function preFilterMCReco(currentObjects, viewObjects) { + emptyCopyObject(currentObjects, viewObjects); + + const associationMCReco = + currentObjects.associations["edm4hep::MCRecoParticleAssociation"]; + + const recoCollection = associationMCReco.map( + (association) => association.from + ); + 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/recoparticletree.js b/js/views/recoparticletree.js new file mode 100644 index 00000000..e72b3c96 --- /dev/null +++ b/js/views/recoparticletree.js @@ -0,0 +1,90 @@ +import { canvas } from "../main.js"; + +export function recoParticleTree(viewCurrentObjects) { + const recoCollection = + viewCurrentObjects.datatypes["edm4hep::ReconstructedParticle"].collection ?? + []; + + if (recoCollection.length === 0) { + 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); +} + +export function recoParticleTreeScroll() { + return { + x: 0, + y: 0, + }; +} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js new file mode 100644 index 00000000..93d7e959 --- /dev/null +++ b/js/views/views-dictionary.js @@ -0,0 +1,39 @@ +import { mcParticleTree, mcParticleTreeScroll } from "./mcparticletree.js"; +import { mcRecoAssociation, preFilterMCReco } from "./mcrecoassociation.js"; +import { + recoParticleTree, + recoParticleTreeScroll, +} from "./recoparticletree.js"; + +export const views = { + mcParticleTree: { + filters: {}, + viewFunction: mcParticleTree, + scrollFunction: mcParticleTreeScroll, + preFilterFunction: (currentObjects, viewObjects) => { + viewObjects.datatypes = {}; + viewObjects.associations = {}; + viewObjects.datatypes["edm4hep::MCParticle"] = + currentObjects.datatypes["edm4hep::MCParticle"]; + }, + }, + recoParticleTree: { + filters: {}, + viewFunction: recoParticleTree, + scrollFunction: recoParticleTreeScroll, + preFilterFunction: (currentObjects, viewObjects) => { + viewObjects.datatypes = {}; + viewObjects.associations = {}; + viewObjects.datatypes["edm4hep::ReconstructedParticle"] = + currentObjects.datatypes["edm4hep::ReconstructedParticle"]; + }, + }, + mcRecoAssociation: { + filters: {}, + viewFunction: mcRecoAssociation, + scrollFunction: () => { + return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; + }, + preFilterFunction: preFilterMCReco, + }, +}; diff --git a/js/views/views.js b/js/views/views.js new file mode 100644 index 00000000..034349e7 --- /dev/null +++ b/js/views/views.js @@ -0,0 +1,125 @@ +import { currentObjects, currentEvent } from "../event-number.js"; +import { copyObject } from "../lib/copy.js"; +import { getVisible } from "../events.js"; +import { drawAll } from "../draw.js"; +import { canvas } from "../main.js"; +import { views } from "./views-dictionary.js"; +import { + mouseDown, + mouseUp, + mouseOut, + mouseMove, + onScroll, +} from "../events.js"; + +const currentView = {}; + +const viewOptions = document.getElementById("view-selector"); + +const scrollLocations = {}; + +function paintButton(view) { + for (const button of buttons) { + if (button.innerText === view) { + button.style.backgroundColor = "#c5c5c5"; + } else { + button.style.backgroundColor = "#f1f1f1"; + } + } +} + +function getViewScrollIndex() { + return `${currentEvent.event}-${getView()}`; +} + +function scroll() { + const index = getViewScrollIndex(); + window.scrollTo(scrollLocations[index].x, scrollLocations[index].y); +} + +const drawView = (view) => { + paintButton(view); + + const dragTools = { + draggedObject: null, + isDragging: false, + prevMouseX: 0, + prevMouseY: 0, + }; + + const { preFilterFunction, viewFunction, scrollFunction, filters } = + views[view]; + + const viewObjects = {}; + const viewCurrentObjects = {}; + const viewVisibleObjects = {}; + + preFilterFunction(currentObjects, viewObjects); + viewFunction(viewObjects); + copyObject(viewObjects, viewCurrentObjects); + + const scrollIndex = getViewScrollIndex(); + + if (scrollLocations[scrollIndex] === undefined) { + const viewScrollLocation = scrollFunction(); + scrollLocations[scrollIndex] = viewScrollLocation; + } + + scroll(); + drawAll(viewCurrentObjects); + getVisible(viewCurrentObjects, viewVisibleObjects); + + canvas.onmousedown = (event) => { + mouseDown(event, viewVisibleObjects, dragTools); + }; + canvas.onmouseup = (event) => { + mouseUp(event, viewCurrentObjects, dragTools); + }; + canvas.onmouseout = (event) => { + mouseOut(event, dragTools); + }; + canvas.onmousemove = (event) => { + mouseMove(event, viewVisibleObjects, dragTools); + }; + window.onscroll = () => { + onScroll(viewCurrentObjects, viewVisibleObjects); + }; + + // here would go distinct filters for each view +}; + +export function saveScrollLocation() { + const index = getViewScrollIndex(); + if (scrollLocations[index] === undefined) return; + scrollLocations[index] = { + x: window.scrollX, + y: window.scrollY, + }; +} + +export const setView = (view) => { + currentView.view = view; +}; + +export const getView = () => { + return currentView.view; +}; + +export const drawCurrentView = () => { + drawView(currentView.view); +}; + +const buttons = []; + +for (const key in views) { + const button = document.createElement("button"); + button.appendChild(document.createTextNode(key)); + button.onclick = () => { + saveScrollLocation(); + setView(key); + drawView(key); + }; + button.className = "view-button"; + buttons.push(button); + viewOptions.appendChild(button); +} From 1229aacbbd1815df335bc2e6080fe3c7caea4e73 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sun, 30 Jun 2024 09:24:53 -0500 Subject: [PATCH 12/21] add proper name for views --- js/views/views-dictionary.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 93d7e959..66270860 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -6,7 +6,7 @@ import { } from "./recoparticletree.js"; export const views = { - mcParticleTree: { + "Monte Carlo Particle Tree": { filters: {}, viewFunction: mcParticleTree, scrollFunction: mcParticleTreeScroll, @@ -17,7 +17,7 @@ export const views = { currentObjects.datatypes["edm4hep::MCParticle"]; }, }, - recoParticleTree: { + "Reconstructed Particle Tree": { filters: {}, viewFunction: recoParticleTree, scrollFunction: recoParticleTreeScroll, @@ -28,7 +28,7 @@ export const views = { currentObjects.datatypes["edm4hep::ReconstructedParticle"]; }, }, - mcRecoAssociation: { + "Monte Carlo-Reconstructed Particle": { filters: {}, viewFunction: mcRecoAssociation, scrollFunction: () => { From 16288eb9410a0fb5db79cd523a08cfa41eabfe07 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sun, 30 Jun 2024 11:42:11 -0500 Subject: [PATCH 13/21] add back filters (temporary solution) --- index.html | 2 +- js/event-number.js | 24 --------------- js/filter/mcparticle.js | 46 ++++++++++++++++++++++++++++ js/main.js | 11 ------- js/menu/filter/filter.js | 58 +++++++++++++++++------------------- js/types/objects.js | 37 ++++++++++++++++++++++- js/views/views-dictionary.js | 7 +++-- js/views/views.js | 3 +- 8 files changed, 115 insertions(+), 73 deletions(-) create mode 100644 js/filter/mcparticle.js diff --git a/index.html b/index.html index 96c0dcce..42e9c369 100644 --- a/index.html +++ b/index.html @@ -161,7 +161,7 @@ - + \ No newline at end of file diff --git a/js/event-number.js b/js/event-number.js index e752f71e..f14540c9 100644 --- a/js/event-number.js +++ b/js/event-number.js @@ -3,16 +3,7 @@ import { copyObject } from "./lib/copy.js"; import { canvas, jsonData, selectedObjectTypes } from "./main.js"; import { objectTypes } from "./types/objects.js"; import { drawCurrentView, saveScrollLocation } from "./views/views.js"; -// import { -// bits, -// genStatus, -// renderRangeParameters, -// parametersRange, -// renderGenSim, -// } from "./menu/filter/filter.js"; -// const filters = document.getElementById("filters"); -// const manipulationTools = document.getElementsByClassName("manipulation-tool"); const eventNumber = document.getElementById("selected-event"); const previousEvent = document.getElementById("previous-event"); const nextEvent = document.getElementById("next-event"); @@ -52,21 +43,6 @@ function loadSelectedEvent() { } else { copyObject(eventCollection[currentEvent.event], currentObjects); } - - // --> menu/filtering stuff - // for (const tool of manipulationTools) { - // tool.style.display = "flex"; - // } - // const mcObjects = loadedObjects.datatypes["edm4hep::MCParticle"].collection; - // genStatus.reset(); - // mcObjects.forEach((mcObject) => { - // genStatus.add(mcObject.generatorStatus); - // }); - // genStatus.setCheckBoxes(); - // filters.replaceChildren(); - - // renderRangeParameters(parametersRange); - // renderGenSim(bits, genStatus); } export function renderEvent(eventNumber) { diff --git a/js/filter/mcparticle.js b/js/filter/mcparticle.js new file mode 100644 index 00000000..6fdf92d7 --- /dev/null +++ b/js/filter/mcparticle.js @@ -0,0 +1,46 @@ +import { PdgToggle } from "../menu/show-pdg.js"; +import { + bits, + genStatus, + renderRangeParameters, + parametersRange, + renderGenSim, + start, + getWidthFilterContent, +} from "../menu/filter/filter.js"; + +const filter = document.getElementById("filter"); +const filters = document.getElementById("filters"); +const manipulationTools = document.getElementsByClassName("manipulation-tool"); + +export function setupMCParticleFilter( + viewObjects, + viewCurrentObjects, + viewVisibleObjects +) { + for (const tool of manipulationTools) { + tool.style.display = "flex"; + } + const mcObjects = + viewCurrentObjects.datatypes["edm4hep::MCParticle"].collection; + genStatus.reset(); + mcObjects.forEach((mcObject) => { + genStatus.add(mcObject.generatorStatus); + }); + genStatus.setCheckBoxes(); + filters.replaceChildren(); + + renderRangeParameters(parametersRange); + renderGenSim(bits, genStatus); + + const width = getWidthFilterContent(); + filter.style.width = width; + const pdgToggle = new PdgToggle("show-pdg"); + pdgToggle.init(() => { + pdgToggle.toggle(viewCurrentObjects, () => { + drawAll(viewCurrentObjects); + }); + }); + + start(viewObjects, viewCurrentObjects, viewVisibleObjects); +} diff --git a/js/main.js b/js/main.js index 39a3f759..bc839d64 100644 --- a/js/main.js +++ b/js/main.js @@ -2,8 +2,6 @@ import { errorMsg } from "./tools.js"; import { renderEvent } from "./event-number.js"; import { setView, getView } from "./views/views.js"; import { views } from "./views/views-dictionary.js"; -// import { PdgToggle } from "./menu/show-pdg.js"; -// import { getWidthFilterContent } from "./menu/filter/filter.js"; const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); @@ -131,15 +129,6 @@ document showEventSwitcher(); showViewsMenu(); renderEvent(eventNum); - - // const width = getWidthFilterContent(); - // filter.style.width = width; - // const pdgToggle = new PdgToggle("show-pdg"); - // pdgToggle.init(() => { - // pdgToggle.toggle(currentObjects, () => { - // drawAll(currentObjects); - // }); - // }); }); export { canvas, ctx, jsonData, selectedObjectTypes }; diff --git a/js/menu/filter/filter.js b/js/menu/filter/filter.js index a764dd09..60724f6a 100644 --- a/js/menu/filter/filter.js +++ b/js/menu/filter/filter.js @@ -1,10 +1,4 @@ import { drawAll } from "../../draw.js"; -import { - ctx, - loadedObjects, - currentObjects, - visibleObjects, -} from "../../main.js"; import { CheckboxBuilder, BitFieldBuilder } from "./builders.js"; import { Range, Checkbox, buildCriteriaFunction } from "./parameters.js"; import { reconnect } from "./reconnect.js"; @@ -22,20 +16,6 @@ const reset = document.getElementById("filter-reset"); let active = false; -filterButton.addEventListener("click", () => { - active = !active; - - if (active) { - openFilter.style.display = "none"; - closeFilter.style.display = "block"; - filterContent.style.display = "flex"; - } else { - openFilter.style.display = "block"; - closeFilter.style.display = "none"; - filterContent.style.display = "none"; - } -}); - export function renderRangeParameters(rangeParameters) { const rangeFilters = document.createElement("div"); rangeFilters.id = "range-filters"; @@ -133,18 +113,34 @@ function removeFilter(loadedObjects, currentObjects, visibleObjects) { renderGenSim(bits, genStatus); } -apply.addEventListener("click", () => - applyFilter(loadedObjects, currentObjects, visibleObjects) -); +export function start(loadedObjects, currentObjects, visibleObjects) { + filterButton.addEventListener("click", () => { + active = !active; + + if (active) { + openFilter.style.display = "none"; + closeFilter.style.display = "block"; + filterContent.style.display = "flex"; + } else { + openFilter.style.display = "block"; + closeFilter.style.display = "none"; + filterContent.style.display = "none"; + } + }); + + apply.addEventListener("click", () => + applyFilter(loadedObjects, currentObjects, visibleObjects) + ); -document.addEventListener("keydown", (event) => { - if (event.key === "Enter" && active) { - applyFilter(loadedObjects, currentObjects, visibleObjects); - } -}); + document.addEventListener("keydown", (event) => { + if (event.key === "Enter" && active) { + applyFilter(loadedObjects, currentObjects, visibleObjects); + } + }); -reset.addEventListener("click", () => - removeFilter(loadedObjects, currentObjects, visibleObjects) -); + reset.addEventListener("click", () => + removeFilter(loadedObjects, currentObjects, visibleObjects) + ); +} export { bits, genStatus, parametersRange }; diff --git a/js/types/objects.js b/js/types/objects.js index 8c2f6bef..2196b94d 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -32,7 +32,6 @@ class EDMObject { y < this.y + this.height ); } - // more methods common to all particles } export class Cluster extends EDMObject { @@ -53,7 +52,43 @@ export class ReconstructedParticle extends EDMObject { } 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) {} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 66270860..86d00b70 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -4,10 +4,11 @@ import { recoParticleTree, recoParticleTreeScroll, } from "./recoparticletree.js"; +import { setupMCParticleFilter } from "../filter/mcparticle.js"; export const views = { "Monte Carlo Particle Tree": { - filters: {}, + filters: setupMCParticleFilter, viewFunction: mcParticleTree, scrollFunction: mcParticleTreeScroll, preFilterFunction: (currentObjects, viewObjects) => { @@ -18,7 +19,7 @@ export const views = { }, }, "Reconstructed Particle Tree": { - filters: {}, + filters: () => {}, viewFunction: recoParticleTree, scrollFunction: recoParticleTreeScroll, preFilterFunction: (currentObjects, viewObjects) => { @@ -29,7 +30,7 @@ export const views = { }, }, "Monte Carlo-Reconstructed Particle": { - filters: {}, + filters: () => {}, viewFunction: mcRecoAssociation, scrollFunction: () => { return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; diff --git a/js/views/views.js b/js/views/views.js index 034349e7..ce655a2a 100644 --- a/js/views/views.js +++ b/js/views/views.js @@ -68,6 +68,7 @@ const drawView = (view) => { scroll(); drawAll(viewCurrentObjects); getVisible(viewCurrentObjects, viewVisibleObjects); + filters(viewObjects, viewCurrentObjects, viewVisibleObjects); canvas.onmousedown = (event) => { mouseDown(event, viewVisibleObjects, dragTools); @@ -84,8 +85,6 @@ const drawView = (view) => { window.onscroll = () => { onScroll(viewCurrentObjects, viewVisibleObjects); }; - - // here would go distinct filters for each view }; export function saveScrollLocation() { From 0dc59888d8425b65b347cf9084cc91fa0a2d099c Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 1 Jul 2024 18:32:05 -0500 Subject: [PATCH 14/21] fix pdg toggle for mcparticle tree view --- js/filter/mcparticle.js | 1 + js/menu/show-pdg.js | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/js/filter/mcparticle.js b/js/filter/mcparticle.js index 6fdf92d7..f29bb0bb 100644 --- a/js/filter/mcparticle.js +++ b/js/filter/mcparticle.js @@ -8,6 +8,7 @@ import { start, getWidthFilterContent, } from "../menu/filter/filter.js"; +import { drawAll } from "../draw.js"; const filter = document.getElementById("filter"); const filters = document.getElementById("filters"); diff --git a/js/menu/show-pdg.js b/js/menu/show-pdg.js index a5333e59..131bbd2e 100644 --- a/js/menu/show-pdg.js +++ b/js/menu/show-pdg.js @@ -1,5 +1,4 @@ import { Toggle } from "./toggle.js"; -import { selectedObjectTypes } from "../main.js"; export class PdgToggle extends Toggle { constructor(id) { @@ -7,23 +6,17 @@ export class PdgToggle extends Toggle { } toggle(currentObjects, redraw) { - const validObjects = selectedObjectTypes.types; - + const collection = + currentObjects.datatypes["edm4hep::MCParticle"].collection; if (this.isSliderActive) { - for (const objectType of validObjects) { - const collection = currentObjects[objectType].collection; - if (collection[0].PDG === undefined) return; - for (const object of collection) { - object.updateTexImg(`${object.PDG}`); - } + if (collection[0].PDG === undefined) return; + for (const object of collection) { + object.updateTexImg(`${object.PDG}`); } } else { - for (const objectType of validObjects) { - const collection = currentObjects[objectType].collection; - if (collection[0].PDG === undefined) return; - for (const object of collection) { - object.updateTexImg(`${object.name}`); - } + if (collection[0].PDG === undefined) return; + for (const object of collection) { + object.updateTexImg(`${object.name}`); } } From 8279495c02ee5118d509ee8e3252824f4ef8e310 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 2 Jul 2024 20:35:19 -0500 Subject: [PATCH 15/21] 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 From c8c2caf7c83014b26c437130271c054917c777b8 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 2 Jul 2024 21:07:30 -0500 Subject: [PATCH 16/21] fix exports for tests --- js/types/links.js | 2 +- js/types/objects.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/types/links.js b/js/types/links.js index 524387d7..4934a72d 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -13,7 +13,7 @@ function generateRandomColor() { return "#" + ((0xffffff * Math.random()) << 0).toString(16).padStart(6, "0"); } -class Link { +export class Link { // we may create a specific class for each type if needed constructor(from, to) { this.from = from; diff --git a/js/types/objects.js b/js/types/objects.js index f633829c..6f05ea37 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -36,7 +36,7 @@ class EDMObject { } } -class MCParticle extends EDMObject { +export class MCParticle extends EDMObject { constructor() { super(); From 3bf0b3eba857ab13fdcc0f0f1c3961605ad803fc Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Wed, 3 Jul 2024 21:49:42 -0500 Subject: [PATCH 17/21] add views for 1:many relations and draw different objects with different objects/colors and its properties --- js/graphic-primitives.js | 59 ------------ js/lib/graphic-primitives.js | 109 +++++++++++++++++++++ js/{tools.js => lib/messages.js} | 0 js/lib/parseCharge.js | 18 ++++ js/main.js | 2 +- js/types/links.js | 96 +++---------------- js/types/objects.js | 156 +++++++++++++++++-------------- js/views/association-view.js | 6 +- js/views/oneToMany.js | 71 ++++++++++++++ js/views/pre-filter.js | 21 +++++ js/views/recocluster.js | 21 +++++ js/views/recotrack.js | 21 +++++ js/views/tree.js | 11 ++- js/views/views-dictionary.js | 14 +-- test/primitives.test.js | 4 +- test/tools.test.js | 2 +- 16 files changed, 388 insertions(+), 223 deletions(-) delete mode 100644 js/graphic-primitives.js create mode 100644 js/lib/graphic-primitives.js rename js/{tools.js => lib/messages.js} (100%) create mode 100644 js/lib/parseCharge.js create mode 100644 js/views/oneToMany.js create mode 100644 js/views/recocluster.js create mode 100644 js/views/recotrack.js diff --git a/js/graphic-primitives.js b/js/graphic-primitives.js deleted file mode 100644 index e9aec198..00000000 --- a/js/graphic-primitives.js +++ /dev/null @@ -1,59 +0,0 @@ -export function drawLine(ctx, startX, startY, endX, endY, color) { - ctx.save(); - ctx.strokeStyle = color; - ctx.beginPath(); - ctx.moveTo(startX, startY); - ctx.lineTo(endX, endY); - ctx.stroke(); - ctx.restore(); -} - -export function drawCross(ctx, x, y, color = "#F00") { - const crossLenght = 6; - ctx.save(); - ctx.strokeStyle = color; - ctx.beginPath(); - ctx.moveTo(x - crossLenght, y - crossLenght); - ctx.lineTo(x + crossLenght, y + crossLenght); - ctx.stroke(); - ctx.beginPath(); - ctx.moveTo(x + crossLenght, y - crossLenght); - ctx.lineTo(x - crossLenght, y + crossLenght); - ctx.stroke(); - ctx.restore(); -} - -export function drawRoundedRect(ctx, x, y, width, height, fillColor) { - ctx.save(); - - ctx.fillStyle = fillColor; - ctx.beginPath(); - ctx.roundRect(x, y, width, height, 15); - ctx.fill(); - - ctx.strokeStyle = "black"; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.roundRect(x, y, width, height, 15); - ctx.stroke(); - ctx.restore(); -} - -export function drawTex(ctx, x, y, texImg, maxWidth) { - let scale = (maxWidth * 0.9) / texImg.naturalWidth; - if (scale > 2) { - scale = 2; - } - const tempHeight = texImg.naturalHeight * scale; - const tempWidth = texImg.naturalWidth * scale; - - ctx.save(); - ctx.drawImage( - texImg, - x - tempWidth / 2, - y - tempHeight / 2, - tempWidth, - tempHeight - ); - ctx.restore(); -} diff --git a/js/lib/graphic-primitives.js b/js/lib/graphic-primitives.js new file mode 100644 index 00000000..2e3438f5 --- /dev/null +++ b/js/lib/graphic-primitives.js @@ -0,0 +1,109 @@ +export function drawRoundedRect(ctx, x, y, width, height, fillColor, radius) { + ctx.save(); + + ctx.fillStyle = fillColor; + ctx.beginPath(); + ctx.roundRect(x, y, width, height, radius); + ctx.fill(); + + ctx.strokeStyle = "black"; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.roundRect(x, y, width, height, radius); + ctx.stroke(); + ctx.restore(); +} + +export function drawTex(ctx, x, y, texImg, maxWidth) { + let scale = (maxWidth * 0.9) / texImg.naturalWidth; + if (scale > 2) { + scale = 2; + } + const tempHeight = texImg.naturalHeight * scale; + const tempWidth = texImg.naturalWidth * scale; + + ctx.save(); + ctx.drawImage( + texImg, + x - tempWidth / 2, + y - tempHeight / 2, + tempWidth, + tempHeight + ); + ctx.restore(); +} + +export function drawTextLines(ctx, lines, boxCenterX, y, n) { + ctx.save(); + ctx.font = "16px sans-serif"; + for (const [i, lineText] of lines.entries()) { + ctx.fillText( + lineText, + boxCenterX - ctx.measureText(lineText).width / 2, + y + i * n + ); + } + ctx.restore(); +} + +export function drawBezierLink(ctx, link) { + const boxFrom = link.from; + const boxTo = link.to; + + const fromX = boxFrom.x + boxFrom.width / 2; + const fromY = boxFrom.y + boxFrom.height; + const toX = boxTo.x + boxTo.width / 2; + const toY = boxTo.y; + + if (toY > fromY) { + var cpFromY = (toY - fromY) / 2 + fromY; + var cpToY = cpFromY; + } else { + cpFromY = (fromY - toY) / 2 + fromY; + cpToY = toY - (fromY - toY) / 2; + } + + if (toX > fromX) { + var cpFromX = (toX - fromX) / 4 + fromX; + var cpToX = (3 * (toX - fromX)) / 4 + fromX; + } else { + cpFromX = (3 * (fromX - toX)) / 4 + toX; + cpToX = (fromX - toX) / 4 + toX; + } + + ctx.save(); + ctx.strokeStyle = link.color; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(fromX + link.xShift, fromY); + ctx.bezierCurveTo( + cpFromX + link.xShift, + cpFromY, + cpToX + link.xShift, + cpToY, + toX + link.xShift, + toY + ); + ctx.stroke(); + ctx.restore(); +} + +export function drawStraightLink(ctx, link) { + const boxFrom = link.from; + const boxTo = link.to; + + const fromX = boxFrom.x + boxFrom.width / 2; + const fromY = boxFrom.y + boxFrom.height / 2; + + const toX = boxTo.x + boxTo.width / 2; + const toY = boxTo.y + boxTo.height / 2; + + ctx.save(); + ctx.strokeStyle = link.color; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(fromX, fromY); + ctx.lineTo(toX, toY); + ctx.stroke(); + ctx.restore(); +} diff --git a/js/tools.js b/js/lib/messages.js similarity index 100% rename from js/tools.js rename to js/lib/messages.js diff --git a/js/lib/parseCharge.js b/js/lib/parseCharge.js new file mode 100644 index 00000000..306a2039 --- /dev/null +++ b/js/lib/parseCharge.js @@ -0,0 +1,18 @@ +export function parseCharge(charge) { + if (Math.abs(charge) < 1.0 && charge != 0) { + if (Math.round(charge * 1000) === 667) { + return "q = 2/3 e"; + } + if (Math.round(charge * 1000) === -667) { + return "q = -2/3 e"; + } + if (Math.round(charge * 1000) === 333) { + return "q = 1/3 e"; + } + if (Math.round(charge * 1000) === -333) { + return "q = -1/3 e"; + } + } else { + return "q = " + charge + " e"; + } +} diff --git a/js/main.js b/js/main.js index d81ab7f7..192fcd09 100644 --- a/js/main.js +++ b/js/main.js @@ -1,4 +1,4 @@ -import { errorMsg } from "./tools.js"; +import { errorMsg } from "./lib/messages.js"; import { renderEvent } from "./event-number.js"; import { setView, getView } from "./views/views.js"; import { views } from "./views/views-dictionary.js"; diff --git a/js/types/links.js b/js/types/links.js index 4934a72d..fa19e997 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -1,3 +1,5 @@ +import { drawBezierLink, drawStraightLink } from "../lib/graphic-primitives.js"; + const colors = { "parents": "#AA0000", "daughters": "#00AA00", @@ -5,16 +7,11 @@ const colors = { "tracks": "#AAAA00", "clusters": "#00AAAA", "particles": "#AA00AA", - "mcclusters": "#AA00AA", - "mctracks": "#AA0000", + "mcclusters": "#D8F1A0", + "mctracks": "#fe5e41", }; -function generateRandomColor() { - return "#" + ((0xffffff * Math.random()) << 0).toString(16).padStart(6, "0"); -} - export class Link { - // we may create a specific class for each type if needed constructor(from, to) { this.from = from; this.to = to; @@ -23,64 +20,7 @@ export class Link { } draw(ctx) { - const boxFrom = this.from; - const boxTo = this.to; - - const fromX = boxFrom.x + boxFrom.width / 2; - const fromY = boxFrom.y + boxFrom.height; - const toX = boxTo.x + boxTo.width / 2; - const toY = boxTo.y; - - if (toY > fromY) { - var cpFromY = (toY - fromY) / 2 + fromY; - var cpToY = cpFromY; - } else { - cpFromY = (fromY - toY) / 2 + fromY; - cpToY = toY - (fromY - toY) / 2; - } - - if (toX > fromX) { - var cpFromX = (toX - fromX) / 4 + fromX; - var cpToX = (3 * (toX - fromX)) / 4 + fromX; - } else { - cpFromX = (3 * (fromX - toX)) / 4 + toX; - cpToX = (fromX - toX) / 4 + toX; - } - - /* - drawCross(ctx, fromX, fromY, "blue"); - drawCross(ctx, toX, toY, "green"); - drawCross(ctx, cpFromX, cpFromY, "yellow"); - drawLine(ctx, fromX, fromY, cpFromX, cpFromY, "yellow") - drawCross(ctx, cpToX, cpToY, "orange"); - drawLine(ctx, toX, toY, cpToX, cpToY, "orange") - */ - - ctx.save(); - ctx.strokeStyle = this.color; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(fromX + this.xShift, fromY); - ctx.bezierCurveTo( - cpFromX + this.xShift, - cpFromY, - cpToX + this.xShift, - cpToY, - toX + this.xShift, - toY - ); - ctx.stroke(); - ctx.restore(); - - /* - ctx.save(); - ctx.font = "14px sans-serif"; - ctx.fillStyle = this.color; - const idText = "ID: " + this.id; - ctx.fillText(idText, - cpToX, cpToY); - ctx.restore(); - */ + drawBezierLink(ctx, this); } isVisible(x, y, width, height) { @@ -141,23 +81,7 @@ class MCRecoParticleAssociation extends Link { } draw(ctx) { - const boxFrom = this.from; - const boxTo = this.to; - - const fromX = boxFrom.x + boxFrom.width / 2; - const fromY = boxFrom.y + boxFrom.height / 2; - - const toX = boxTo.x + boxTo.width / 2; - const toY = boxTo.y + boxTo.height / 2; - - ctx.save(); - ctx.strokeStyle = this.color; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(fromX, fromY); - ctx.lineTo(toX, toY); - ctx.stroke(); - ctx.restore(); + drawStraightLink(ctx, this); } } @@ -188,6 +112,10 @@ class MCRecoTrackParticleAssociation extends Link { this.color = colors["mctracks"]; this.weight = weight; } + + draw(ctx) { + drawStraightLink(ctx, this); + } } class MCRecoClusterParticleAssociation extends Link { @@ -196,6 +124,10 @@ class MCRecoClusterParticleAssociation extends Link { this.color = colors["mcclusters"]; this.weight = weight; } + + draw(ctx) { + drawStraightLink(ctx, this); + } } export const linkTypes = { diff --git a/js/types/objects.js b/js/types/objects.js index 6f05ea37..28d0ed52 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -1,6 +1,11 @@ -import { drawTex, drawRoundedRect } from "../graphic-primitives.js"; +import { + drawTex, + drawRoundedRect, + drawTextLines, +} from "../lib/graphic-primitives.js"; import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; +import { parseCharge } from "../lib/parseCharge.js"; class EDMObject { constructor() { @@ -60,7 +65,15 @@ export class MCParticle extends EDMObject { draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#dff6ff", + 15 + ); if (this.texImg.complete) { drawTex( @@ -94,41 +107,11 @@ export class MCParticle extends EDMObject { bottomLines.push("d = " + this.vertex + " mm"); bottomLines.push("t = " + this.time + " ns"); bottomLines.push("m = " + this.mass + " GeV"); - if (Math.abs(this.charge) < 1.0 && this.charge != 0) { - if (Math.round(this.charge * 1000) === 667) { - bottomLines.push("q = 2/3 e"); - } - if (Math.round(this.charge * 1000) === -667) { - bottomLines.push("q = -2/3 e"); - } - if (Math.round(this.charge * 1000) === 333) { - bottomLines.push("q = 1/3 e"); - } - if (Math.round(this.charge * 1000) === -333) { - bottomLines.push("q = -1/3 e"); - } - } else { - bottomLines.push("q = " + this.charge + " e"); - } + bottomLines.push(parseCharge(this.charge)); - 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 - ); - } + drawTextLines(ctx, topLines, boxCenterX, topY, 23); - for (const [i, lineText] of bottomLines.entries()) { - ctx.fillText( - lineText, - boxCenterX - ctx.measureText(lineText).width / 2, - bottomY + i * 22 - ); - } - ctx.restore(); + drawTextLines(ctx, bottomLines, boxCenterX, bottomY, 22); } static setup(mcCollection) { @@ -216,46 +199,32 @@ export class MCParticle extends EDMObject { class ReconstructedParticle extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 180; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#fbffdf", + 30 + ); const topY = this.y + 20; - const topLines = []; - topLines.push("ID: " + this.index); + const lines = []; + lines.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"); - } + lines.push("e = " + energy + " GeV"); + lines.push(parseCharge(this.charge)); + lines.push("pdg = " + this.PDG); - 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(); + drawTextLines(ctx, lines, boxCenterX, topY, 23); } static setup(recoCollection) {} @@ -266,12 +235,37 @@ class ReconstructedParticle extends EDMObject { class Cluster extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 180; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#ffe8df", + 20 + ); + + const topY = this.y + 20; + const lines = []; + lines.push("ID: " + this.index); + lines.push("type: " + this.type); + const energy = parseInt(this.energy * 100) / 100; + lines.push("e = " + energy + " GeV"); + const x = parseInt(this.position.x * 100) / 100; + const y = parseInt(this.position.y * 100) / 100; + const z = parseInt(this.position.z * 100) / 100; + lines.push(`pos = (${x},`); + lines.push(`${y},`); + lines.push(`${z}) mm`); + + drawTextLines(ctx, lines, boxCenterX, topY, 23); } static setup(clusterCollection) {} @@ -280,12 +274,38 @@ class Cluster extends EDMObject { class Track extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 180; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#fff6df", + 25 + ); + + const topY = this.y + 20; + + const lines = []; + lines.push("ID: " + this.index); + lines.push("type: " + this.type); + const chi2 = parseInt(this.chi2 * 100) / 100; + const ndf = parseInt(this.ndf * 100) / 100; + const chiNdf = `${chi2} / ${ndf}`; + lines.push("chi2/ndf = " + chiNdf); + lines.push("dEdx = " + this.dEdx); + + const trackerHitsCount = this.oneToManyRelations["trackerHits"].length; + lines.push("tracker hits: " + trackerHitsCount); + + drawTextLines(ctx, lines, boxCenterX, topY, 23); } static setup(trackCollection) {} diff --git a/js/views/association-view.js b/js/views/association-view.js index 998793e5..92bcd3ff 100644 --- a/js/views/association-view.js +++ b/js/views/association-view.js @@ -37,13 +37,15 @@ export function buildAssociationView(viewObjects, associationName) { canvas.height = fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; + const fromX = width / 2 - fromWidth - fromHorizontalGap; for (const [index, from] of fromCollection.entries()) { from.y = fromVerticalGap + index * (fromHeight + fromVerticalGap); - from.x = width / 2 + fromHorizontalGap; + from.x = fromX; } + const toX = width / 2 + toHorizontalGap; for (const [index, to] of toCollection.entries()) { to.y = toVerticalGap + index * (toHeight + toVerticalGap); - to.x = width / 2 - toWidth - toHorizontalGap; + to.x = toX; } } diff --git a/js/views/oneToMany.js b/js/views/oneToMany.js new file mode 100644 index 00000000..07cd179e --- /dev/null +++ b/js/views/oneToMany.js @@ -0,0 +1,71 @@ +import { canvas } from "../main.js"; + +// One to Many relation between two collections +export function oneToManyView( + viewCurrentObjects, + fromCollectionName, + toCollectionName, + relationName +) { + const fromCollection = + viewCurrentObjects.datatypes[fromCollectionName].collection; + + const toCollection = + viewCurrentObjects.datatypes[toCollectionName].collection; + + if (fromCollection.length === 0 || toCollection.length === 0) { + alert("No association found!"); + return; + } + + const fromWidth = fromCollection[0].width; + const fromHeight = fromCollection[0].height; + const fromVerticalGap = 0.3 * fromHeight; + const fromHorizontalGap = 0.3 * fromWidth; + + const toWidth = toCollection[0].width; + const toHeight = toCollection[0].height; + const toVerticalGap = 0.3 * toHeight; + const toHorizontalGap = 0.3 * toWidth; + + const gap = 2 * (fromWidth + toWidth); + const totalWidth = gap + fromWidth + toWidth; + + const fromTotalHeight = + fromCollection.length * (fromHeight + fromVerticalGap) + fromVerticalGap; + + const toTotalHeight = + toCollection.length * (toHeight + toVerticalGap) + toVerticalGap; + + canvas.height = + fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; + + canvas.width = + totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + + const width = canvas.width; + + const fromX = width / 2 - fromWidth - fromHorizontalGap; + const toX = width / 2 + toHorizontalGap; + + let toCount = 0; + + fromCollection.forEach((fromObject) => { + const objectRelations = fromObject.oneToManyRelations[relationName]; + const length = objectRelations.length; + + objectRelations.forEach((relation) => { + const toObject = relation.to; + + toObject.x = toX; + toObject.y = toVerticalGap + toCount * (toHeight + toVerticalGap); + toCount++; + }); + + fromObject.x = fromX; + const firstY = objectRelations[0].to.y; + const lastY = objectRelations[length - 1].to.y; + + fromObject.y = parseInt((firstY + lastY) / 2); + }); +} diff --git a/js/views/pre-filter.js b/js/views/pre-filter.js index 8334ee6b..af2a4fd1 100644 --- a/js/views/pre-filter.js +++ b/js/views/pre-filter.js @@ -37,3 +37,24 @@ export function preFilterTree( currentObjects.datatypes[collectionName].oneToMany[relationName]; }); } + +export function preFilterOneToMany( + currentObjects, + viewObjects, + fromCollectionName, + toCollectionName, + relationName +) { + emptyCopyObject(currentObjects, viewObjects); + + const relations = + currentObjects.datatypes[fromCollectionName].oneToMany[relationName]; + + viewObjects.datatypes[fromCollectionName].oneToMany[relationName] = relations; + + const fromCollection = relations.map((relation) => relation.from); + const toCollection = relations.map((relation) => relation.to); + + viewObjects.datatypes[fromCollectionName].collection = fromCollection; + viewObjects.datatypes[toCollectionName].collection = toCollection; +} diff --git a/js/views/recocluster.js b/js/views/recocluster.js new file mode 100644 index 00000000..72b92e17 --- /dev/null +++ b/js/views/recocluster.js @@ -0,0 +1,21 @@ +import { preFilterOneToMany } from "./pre-filter.js"; +import { oneToManyView } from "./oneToMany.js"; + +export function recoClusterView(viewCurrentObjects) { + oneToManyView( + viewCurrentObjects, + "edm4hep::ReconstructedParticle", + "edm4hep::Cluster", + "clusters" + ); +} + +export function preFilterRecoCluster(currentObjects, viewObjects) { + preFilterOneToMany( + currentObjects, + viewObjects, + "edm4hep::ReconstructedParticle", + "edm4hep::Cluster", + "clusters" + ); +} diff --git a/js/views/recotrack.js b/js/views/recotrack.js new file mode 100644 index 00000000..39b1793a --- /dev/null +++ b/js/views/recotrack.js @@ -0,0 +1,21 @@ +import { preFilterOneToMany } from "./pre-filter.js"; +import { oneToManyView } from "./oneToMany.js"; + +export function recoTrackView(viewCurrentObjects) { + oneToManyView( + viewCurrentObjects, + "edm4hep::ReconstructedParticle", + "edm4hep::Track", + "tracks" + ); +} + +export function preFilterRecoTrack(currentObjects, viewObjects) { + preFilterOneToMany( + currentObjects, + viewObjects, + "edm4hep::ReconstructedParticle", + "edm4hep::Track", + "tracks" + ); +} diff --git a/js/views/tree.js b/js/views/tree.js index e57f6ffc..bfe89342 100644 --- a/js/views/tree.js +++ b/js/views/tree.js @@ -65,8 +65,15 @@ export function buildTree(collection, relationOfReference) { }); }); - canvas.width = + const totalWidth = boxWidth * matrix[0].length + horizontalGap * (matrix[0].length + 1); - canvas.height = + + canvas.width = + totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + + const totalHeight = boxHeight * (matrix.length + 1) + verticalGap * (matrix.length + 2); + + canvas.height = + totalHeight > window.innerHeight ? totalHeight : window.innerHeight; } diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index a5783d91..df43922b 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -10,6 +10,8 @@ import { preFilterMCCluster, mcClusterAssociation, } from "./mcclusterassociation.js"; +import { recoTrackView, preFilterRecoTrack } from "./recotrack.js"; +import { recoClusterView, preFilterRecoCluster } from "./recocluster.js"; export const views = { "Monte Carlo Particle Tree": { @@ -38,15 +40,15 @@ export const views = { }, "Reconstructed Particle-Cluster": { filters: () => {}, - viewFunction: () => {}, - scrollFunction: () => {}, - preFilterFunction: () => {}, + viewFunction: recoClusterView, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterRecoCluster, }, "Reconstructed Particle-Track": { filters: () => {}, - viewFunction: () => {}, - scrollFunction: () => {}, - preFilterFunction: () => {}, + viewFunction: recoTrackView, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterRecoTrack, }, "Monte Carlo-Reconstructed Particle": { filters: () => {}, diff --git a/test/primitives.test.js b/test/primitives.test.js index f58cb271..550f9999 100644 --- a/test/primitives.test.js +++ b/test/primitives.test.js @@ -1,5 +1,5 @@ import { jest } from "@jest/globals"; -import { drawRoundedRect, drawTex } from "../js/graphic-primitives.js"; +import { drawRoundedRect, drawTex } from "../js/lib/graphic-primitives.js"; let ctx; @@ -24,7 +24,7 @@ afterEach(() => { describe("drawRoundedRect", () => { it("should draw a rounded rectangle with the correct properties", () => { - drawRoundedRect(ctx, 10, 20, 100, 200, "red"); + drawRoundedRect(ctx, 10, 20, 100, 200, "red", 15); expect(ctx.save).toHaveBeenCalled(); expect(ctx.fillStyle).toBe("red"); diff --git a/test/tools.test.js b/test/tools.test.js index 84cdaa98..7a0ad3f1 100644 --- a/test/tools.test.js +++ b/test/tools.test.js @@ -1,4 +1,4 @@ -import { infoMsg, errorMsg } from "../js/tools"; +import { infoMsg, errorMsg } from "../js/lib/messages.js"; let msgDiv; From a617726339daaa31233c1e347525d9776a0604e7 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 6 Jul 2024 19:18:29 -0500 Subject: [PATCH 18/21] merge reco particle view with cluster and tracks --- js/views/oneToMany.js | 71 ---------------- js/views/pre-filter.js | 21 ----- js/views/recocluster.js | 21 ----- js/views/recoclustertrack.js | 154 +++++++++++++++++++++++++++++++++++ js/views/recotrack.js | 21 ----- js/views/views-dictionary.js | 18 ++-- 6 files changed, 161 insertions(+), 145 deletions(-) delete mode 100644 js/views/oneToMany.js delete mode 100644 js/views/recocluster.js create mode 100644 js/views/recoclustertrack.js delete mode 100644 js/views/recotrack.js diff --git a/js/views/oneToMany.js b/js/views/oneToMany.js deleted file mode 100644 index 07cd179e..00000000 --- a/js/views/oneToMany.js +++ /dev/null @@ -1,71 +0,0 @@ -import { canvas } from "../main.js"; - -// One to Many relation between two collections -export function oneToManyView( - viewCurrentObjects, - fromCollectionName, - toCollectionName, - relationName -) { - const fromCollection = - viewCurrentObjects.datatypes[fromCollectionName].collection; - - const toCollection = - viewCurrentObjects.datatypes[toCollectionName].collection; - - if (fromCollection.length === 0 || toCollection.length === 0) { - alert("No association found!"); - return; - } - - const fromWidth = fromCollection[0].width; - const fromHeight = fromCollection[0].height; - const fromVerticalGap = 0.3 * fromHeight; - const fromHorizontalGap = 0.3 * fromWidth; - - const toWidth = toCollection[0].width; - const toHeight = toCollection[0].height; - const toVerticalGap = 0.3 * toHeight; - const toHorizontalGap = 0.3 * toWidth; - - const gap = 2 * (fromWidth + toWidth); - const totalWidth = gap + fromWidth + toWidth; - - const fromTotalHeight = - fromCollection.length * (fromHeight + fromVerticalGap) + fromVerticalGap; - - const toTotalHeight = - toCollection.length * (toHeight + toVerticalGap) + toVerticalGap; - - canvas.height = - fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; - - canvas.width = - totalWidth > window.innerWidth ? totalWidth : window.innerWidth; - - const width = canvas.width; - - const fromX = width / 2 - fromWidth - fromHorizontalGap; - const toX = width / 2 + toHorizontalGap; - - let toCount = 0; - - fromCollection.forEach((fromObject) => { - const objectRelations = fromObject.oneToManyRelations[relationName]; - const length = objectRelations.length; - - objectRelations.forEach((relation) => { - const toObject = relation.to; - - toObject.x = toX; - toObject.y = toVerticalGap + toCount * (toHeight + toVerticalGap); - toCount++; - }); - - fromObject.x = fromX; - const firstY = objectRelations[0].to.y; - const lastY = objectRelations[length - 1].to.y; - - fromObject.y = parseInt((firstY + lastY) / 2); - }); -} diff --git a/js/views/pre-filter.js b/js/views/pre-filter.js index af2a4fd1..8334ee6b 100644 --- a/js/views/pre-filter.js +++ b/js/views/pre-filter.js @@ -37,24 +37,3 @@ export function preFilterTree( currentObjects.datatypes[collectionName].oneToMany[relationName]; }); } - -export function preFilterOneToMany( - currentObjects, - viewObjects, - fromCollectionName, - toCollectionName, - relationName -) { - emptyCopyObject(currentObjects, viewObjects); - - const relations = - currentObjects.datatypes[fromCollectionName].oneToMany[relationName]; - - viewObjects.datatypes[fromCollectionName].oneToMany[relationName] = relations; - - const fromCollection = relations.map((relation) => relation.from); - const toCollection = relations.map((relation) => relation.to); - - viewObjects.datatypes[fromCollectionName].collection = fromCollection; - viewObjects.datatypes[toCollectionName].collection = toCollection; -} diff --git a/js/views/recocluster.js b/js/views/recocluster.js deleted file mode 100644 index 72b92e17..00000000 --- a/js/views/recocluster.js +++ /dev/null @@ -1,21 +0,0 @@ -import { preFilterOneToMany } from "./pre-filter.js"; -import { oneToManyView } from "./oneToMany.js"; - -export function recoClusterView(viewCurrentObjects) { - oneToManyView( - viewCurrentObjects, - "edm4hep::ReconstructedParticle", - "edm4hep::Cluster", - "clusters" - ); -} - -export function preFilterRecoCluster(currentObjects, viewObjects) { - preFilterOneToMany( - currentObjects, - viewObjects, - "edm4hep::ReconstructedParticle", - "edm4hep::Cluster", - "clusters" - ); -} diff --git a/js/views/recoclustertrack.js b/js/views/recoclustertrack.js new file mode 100644 index 00000000..1ecee9fd --- /dev/null +++ b/js/views/recoclustertrack.js @@ -0,0 +1,154 @@ +import { canvas } from "../main.js"; +import { emptyCopyObject } from "../lib/copy.js"; + +export function recoClusterTrack(viewObjects) { + const recoParticles = + viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection; + + if (recoParticles.length === 0) { + alert("No reconstructed particles found!"); + return; + } + + const findFirstObject = (relationName) => { + const object = recoParticles.find((particle) => { + const relation = particle.oneToManyRelations[relationName]; + if (relation.length > 0) { + return relation[0].to; + } + }); + return object; + }; + + const firstRecoParticle = recoParticles[0]; + const recoHeight = firstRecoParticle.height; + const recoVerticalGap = parseInt(recoHeight * 0.3); + const recoVerticalSpace = recoHeight + recoVerticalGap; + const recoHalfHeight = parseInt(recoHeight / 2); + const recoWidth = firstRecoParticle.width; + const recoHorizontalGap = recoWidth * 0.3; + + const firstCluster = findFirstObject("clusters"); + const clusterHeight = firstCluster.height; + const clusterVerticalGap = clusterHeight * 0.3; + const clusterWidth = firstCluster.width; + const firstTrack = findFirstObject("tracks"); + const trackHeight = firstTrack.height; + const trackVerticalGap = trackHeight * 0.3; + const trackWidth = firstTrack.width; + + const widestObject = Math.max(clusterWidth, trackWidth); + const widestGap = widestObject * 0.3; + + const totalHorizontalGap = + 2 * recoHorizontalGap + recoWidth + widestObject + 2 * widestGap; + + const width = + totalHorizontalGap > window.innerWidth + ? totalHorizontalGap + : window.innerWidth; + + canvas.width = width; + + const recoX = width / 2 - recoWidth; + + const otherX = width / 2 + widestGap; + + let totalHeight = 0; + + recoParticles.forEach((particle) => { + const clusterRelations = particle.oneToManyRelations["clusters"]; + const trackRelations = particle.oneToManyRelations["tracks"]; + + const relationsHeight = parseInt( + clusterRelations.length * (clusterHeight + clusterVerticalGap) + + trackRelations.length * (trackHeight + trackVerticalGap) + ); + + const height = + recoVerticalSpace > relationsHeight ? recoVerticalSpace : relationsHeight; + + const recoY = totalHeight + height / 2 - recoHalfHeight; + particle.y = recoY; + particle.x = recoX; + + const initialGap = (height - relationsHeight) / 2; + + let accumulatedRelationsHeight = initialGap + totalHeight; + + clusterRelations.forEach((clusterRelation) => { + const cluster = clusterRelation.to; + cluster.x = otherX; + + const y = clusterVerticalGap / 2 + accumulatedRelationsHeight; + cluster.y = y; + accumulatedRelationsHeight += clusterHeight + clusterVerticalGap / 2; + }); + + trackRelations.forEach((trackRelation) => { + const track = trackRelation.to; + track.x = otherX; + + const y = trackVerticalGap / 2 + accumulatedRelationsHeight; + track.y = y; + accumulatedRelationsHeight += trackHeight + trackVerticalGap / 2; + }); + + totalHeight += height; + }); + + canvas.height = totalHeight; +} + +export function preFilterRecoClusterTrack(currentObjects, viewObjects) { + emptyCopyObject(currentObjects, viewObjects); + + const fromDatatype = + currentObjects.datatypes["edm4hep::ReconstructedParticle"]; + + const fromCollection = fromDatatype.collection; + + const recoParticles = []; + const clusters = []; + const tracks = []; + + fromCollection.forEach((particle) => { + const clusterRelations = particle.oneToManyRelations["clusters"]; + const trackRelations = particle.oneToManyRelations["tracks"]; + + const total = clusterRelations.length + trackRelations.length; + + if (total === 0) { + return; + } + + clusterRelations.forEach((clusterRelation) => { + const cluster = clusterRelation.to; + clusters.push(cluster); + }); + + trackRelations.forEach((trackRelation) => { + const track = trackRelation.to; + tracks.push(track); + }); + + recoParticles.push(particle); + }); + + viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection = + recoParticles; + viewObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany[ + "clusters" + ] = + currentObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany[ + "clusters" + ]; + viewObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany["tracks"] = + currentObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany[ + "tracks" + ]; + + viewObjects.datatypes["edm4hep::Cluster"].collection = clusters; + + viewObjects.datatypes["edm4hep::Track"].collection = tracks; +} diff --git a/js/views/recotrack.js b/js/views/recotrack.js deleted file mode 100644 index 39b1793a..00000000 --- a/js/views/recotrack.js +++ /dev/null @@ -1,21 +0,0 @@ -import { preFilterOneToMany } from "./pre-filter.js"; -import { oneToManyView } from "./oneToMany.js"; - -export function recoTrackView(viewCurrentObjects) { - oneToManyView( - viewCurrentObjects, - "edm4hep::ReconstructedParticle", - "edm4hep::Track", - "tracks" - ); -} - -export function preFilterRecoTrack(currentObjects, viewObjects) { - preFilterOneToMany( - currentObjects, - viewObjects, - "edm4hep::ReconstructedParticle", - "edm4hep::Track", - "tracks" - ); -} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index df43922b..80384920 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -10,8 +10,10 @@ import { preFilterMCCluster, mcClusterAssociation, } from "./mcclusterassociation.js"; -import { recoTrackView, preFilterRecoTrack } from "./recotrack.js"; -import { recoClusterView, preFilterRecoCluster } from "./recocluster.js"; +import { + recoClusterTrack, + preFilterRecoClusterTrack, +} from "./recoclustertrack.js"; export const views = { "Monte Carlo Particle Tree": { @@ -38,17 +40,11 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterClusterTree, }, - "Reconstructed Particle-Cluster": { - filters: () => {}, - viewFunction: recoClusterView, - scrollFunction: scrollTopCenter, - preFilterFunction: preFilterRecoCluster, - }, - "Reconstructed Particle-Track": { + "Reco Particle-Cluster-Track": { filters: () => {}, - viewFunction: recoTrackView, + viewFunction: recoClusterTrack, scrollFunction: scrollTopCenter, - preFilterFunction: preFilterRecoTrack, + preFilterFunction: preFilterRecoClusterTrack, }, "Monte Carlo-Reconstructed Particle": { filters: () => {}, From 9b592d223e6b664ac37048d6b9e076573e4f3470 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 6 Jul 2024 19:37:36 -0500 Subject: [PATCH 19/21] fix associations views when different heights --- js/views/association-view.js | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/js/views/association-view.js b/js/views/association-view.js index 92bcd3ff..00eeb28c 100644 --- a/js/views/association-view.js +++ b/js/views/association-view.js @@ -19,33 +19,33 @@ export function buildAssociationView(viewObjects, associationName) { const gap = 2 * (fromWidth + toWidth); const totalWidth = gap + fromWidth + toWidth; - canvas.width = - totalWidth > window.innerWidth ? totalWidth : window.innerWidth; - - const width = canvas.width; + const width = totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + canvas.width = 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; + const height = Math.max(fromHeight, toHeight); + const verticalGap = 0.3 * height; + + const totalHeight = + fromCollection.length * (height + verticalGap) + verticalGap; + + canvas.height = totalHeight; - canvas.height = - fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; + let accHeight = 0; const fromX = width / 2 - fromWidth - fromHorizontalGap; - for (const [index, from] of fromCollection.entries()) { - from.y = fromVerticalGap + index * (fromHeight + fromVerticalGap); - from.x = fromX; - } const toX = width / 2 + toHorizontalGap; - for (const [index, to] of toCollection.entries()) { - to.y = toVerticalGap + index * (toHeight + toVerticalGap); - to.x = toX; + + for (let i = 0; i < fromCollection.length; i++) { + fromCollection[i].x = fromX; + toCollection[i].x = toX; + + const space = height + verticalGap; + fromCollection[i].y = accHeight + space / 2 - fromHeight / 2; + toCollection[i].y = accHeight + space / 2 - toHeight / 2; + accHeight += height + verticalGap; } } From 4806d73d197fb10436cb7451f13a45682209dd7e Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 6 Jul 2024 19:46:46 -0500 Subject: [PATCH 20/21] show momentum, energy and charge for reco particle --- js/types/objects.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/js/types/objects.js b/js/types/objects.js index 28d0ed52..1bf8d773 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -218,11 +218,20 @@ class ReconstructedParticle extends EDMObject { const topY = this.y + 20; const lines = []; + lines.push("ID: " + this.index); + + const x = parseInt(this.momentum.x * 100) / 100; + const y = parseInt(this.momentum.y * 100) / 100; + const z = parseInt(this.momentum.z * 100) / 100; + lines.push(`p = (x=${x},`); + lines.push(`y=${y},`); + lines.push(`z=${z}) GeV`); + const energy = parseInt(this.energy * 100) / 100; lines.push("e = " + energy + " GeV"); + lines.push(parseCharge(this.charge)); - lines.push("pdg = " + this.PDG); drawTextLines(ctx, lines, boxCenterX, topY, 23); } @@ -261,9 +270,9 @@ class Cluster extends EDMObject { const x = parseInt(this.position.x * 100) / 100; const y = parseInt(this.position.y * 100) / 100; const z = parseInt(this.position.z * 100) / 100; - lines.push(`pos = (${x},`); - lines.push(`${y},`); - lines.push(`${z}) mm`); + lines.push(`pos = (x=${x},`); + lines.push(`y=${y},`); + lines.push(`z=${z}) mm`); drawTextLines(ctx, lines, boxCenterX, topY, 23); } From de191b1e46bf171227d5b75cff936078e1b04a51 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 6 Jul 2024 20:20:08 -0500 Subject: [PATCH 21/21] show filters only on mc view --- js/filter/nofilter.js | 7 +++++++ js/menu/filter/reconnect.js | 8 ++++---- js/views/views-dictionary.js | 15 ++++++++------- 3 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 js/filter/nofilter.js diff --git a/js/filter/nofilter.js b/js/filter/nofilter.js new file mode 100644 index 00000000..0e41a3fc --- /dev/null +++ b/js/filter/nofilter.js @@ -0,0 +1,7 @@ +export function setupNoFilter() { + const manipulationTools = + document.getElementsByClassName("manipulation-tool"); + for (const tool of manipulationTools) { + tool.style.display = "none"; + } +} diff --git a/js/menu/filter/reconnect.js b/js/menu/filter/reconnect.js index 4ff272b6..28ccac22 100644 --- a/js/menu/filter/reconnect.js +++ b/js/menu/filter/reconnect.js @@ -6,11 +6,11 @@ export function reconnect(criteriaFunction, loadedObjects) { emptyCopyObject(loadedObjects, filteredObjects); - for (const [key, value] of Object.entries(loadedObjects.datatypes)) { - const filterFunction = objectTypes[key].filter; + const filterFunction = objectTypes["edm4hep::MCParticle"].filter; - filterFunction(value, filteredObjects.datatypes, criteriaFunction); - } + const mcParticles = loadedObjects.datatypes["edm4hep::MCParticle"]; + + filterFunction(mcParticles, filteredObjects.datatypes, criteriaFunction); return filteredObjects; } diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 80384920..716983d1 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -14,6 +14,7 @@ import { recoClusterTrack, preFilterRecoClusterTrack, } from "./recoclustertrack.js"; +import { setupNoFilter } from "../filter/nofilter.js"; export const views = { "Monte Carlo Particle Tree": { @@ -23,43 +24,43 @@ export const views = { preFilterFunction: preFilterMCTree, }, "Reconstructed Particle Tree": { - filters: () => {}, + filters: setupNoFilter, viewFunction: recoParticleTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterRecoTree, }, "Track Tree": { - filters: () => {}, + filters: setupNoFilter, viewFunction: trackTree, scrollFunction: scrollTopCenter, preFilterFunction: preFilterTrackTree, }, "Cluster Tree": { - filters: () => {}, + filters: setupNoFilter, viewFunction: clusterTree, scrollFunction: scrollTopCenter, preFilterFunction: preFilterClusterTree, }, "Reco Particle-Cluster-Track": { - filters: () => {}, + filters: setupNoFilter, viewFunction: recoClusterTrack, scrollFunction: scrollTopCenter, preFilterFunction: preFilterRecoClusterTrack, }, "Monte Carlo-Reconstructed Particle": { - filters: () => {}, + filters: setupNoFilter, viewFunction: mcRecoAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCReco, }, "Monte Carlo Particle-Track": { - filters: () => {}, + filters: setupNoFilter, viewFunction: mcTrackAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCTrack, }, "Monte Carlo Particle-Cluster": { - filters: () => {}, + filters: setupNoFilter, viewFunction: mcClusterAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCCluster,