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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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}`); } }