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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] fix pdg toggle for mcparticle tree view --- js/filter/mcparticle.js | 1 + js/menu/show-pdg.js | 23 ++++++++--------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/js/filter/mcparticle.js b/js/filter/mcparticle.js index 6fdf92d7..f29bb0bb 100644 --- a/js/filter/mcparticle.js +++ b/js/filter/mcparticle.js @@ -8,6 +8,7 @@ import { start, getWidthFilterContent, } from "../menu/filter/filter.js"; +import { drawAll } from "../draw.js"; const filter = document.getElementById("filter"); const filters = document.getElementById("filters"); diff --git a/js/menu/show-pdg.js b/js/menu/show-pdg.js index a5333e59..131bbd2e 100644 --- a/js/menu/show-pdg.js +++ b/js/menu/show-pdg.js @@ -1,5 +1,4 @@ import { Toggle } from "./toggle.js"; -import { selectedObjectTypes } from "../main.js"; export class PdgToggle extends Toggle { constructor(id) { @@ -7,23 +6,17 @@ export class PdgToggle extends Toggle { } toggle(currentObjects, redraw) { - const validObjects = selectedObjectTypes.types; - + const collection = + currentObjects.datatypes["edm4hep::MCParticle"].collection; if (this.isSliderActive) { - for (const objectType of validObjects) { - const collection = currentObjects[objectType].collection; - if (collection[0].PDG === undefined) return; - for (const object of collection) { - object.updateTexImg(`${object.PDG}`); - } + if (collection[0].PDG === undefined) return; + for (const object of collection) { + object.updateTexImg(`${object.PDG}`); } } else { - for (const objectType of validObjects) { - const collection = currentObjects[objectType].collection; - if (collection[0].PDG === undefined) return; - for (const object of collection) { - object.updateTexImg(`${object.name}`); - } + if (collection[0].PDG === undefined) return; + for (const object of collection) { + object.updateTexImg(`${object.name}`); } } From 8279495c02ee5118d509ee8e3252824f4ef8e310 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 2 Jul 2024 20:35:19 -0500 Subject: [PATCH 15/60] add view for cluster, track and mc with each one --- css/views.css | 20 ++++ index.html | 4 +- js/event-number.js | 4 +- js/main.js | 4 + js/types/links.js | 60 ++++++++--- js/types/load.js | 9 -- js/types/objects.js | 174 +++++++++++++++++-------------- js/views/association-view.js | 49 +++++++++ js/views/clustertree.js | 18 ++++ js/views/mcclusterassociation.js | 19 ++++ js/views/mcparticletree.js | 8 +- js/views/mcrecoassociation.js | 76 ++------------ js/views/mctrackassociation.js | 16 +++ js/views/pre-filter.js | 39 +++++++ js/views/recoparticletree.js | 84 ++------------- js/views/scrolls.js | 9 ++ js/views/tracktree.js | 18 ++++ js/views/tree.js | 72 +++++++++++++ js/views/views-dictionary.js | 73 +++++++++---- model/index.js | 2 + output/datatypes.js | 34 ++++++ 21 files changed, 521 insertions(+), 271 deletions(-) create mode 100644 js/views/association-view.js create mode 100644 js/views/clustertree.js create mode 100644 js/views/mcclusterassociation.js create mode 100644 js/views/mctrackassociation.js create mode 100644 js/views/pre-filter.js create mode 100644 js/views/scrolls.js create mode 100644 js/views/tracktree.js create mode 100644 js/views/tree.js diff --git a/css/views.css b/css/views.css index f6ebf157..86e4c0c8 100644 --- a/css/views.css +++ b/css/views.css @@ -5,6 +5,8 @@ justify-content: flex-start; align-items: center; padding: 8px; + max-height: 90px; + overflow-y: auto; } .view-button { @@ -39,3 +41,21 @@ overflow-x: hidden; width: fit-content; } + +.view-selector-menu::-webkit-scrollbar { + width: 7px; +} + +.view-selector-menu::-webkit-scrollbar-track { + background: #e1e1e1; + border-radius: 5px; +} + +.view-selector-menu::-webkit-scrollbar-thumb { + background: #afafaf; + border-radius: 5px; +} + +.view-selector-menu::-webkit-scrollbar-thumb:hover { + background: #858585; +} diff --git a/index.html b/index.html index 42e9c369..3cb6744e 100644 --- a/index.html +++ b/index.html @@ -42,7 +42,7 @@ -
+

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

Select a view:

-
+
diff --git a/js/event-number.js b/js/event-number.js index f14540c9..83b773ad 100644 --- a/js/event-number.js +++ b/js/event-number.js @@ -1,6 +1,6 @@ import { loadObjects } from "./types/load.js"; import { copyObject } from "./lib/copy.js"; -import { canvas, jsonData, selectedObjectTypes } from "./main.js"; +import { jsonData, selectedObjectTypes } from "./main.js"; import { objectTypes } from "./types/objects.js"; import { drawCurrentView, saveScrollLocation } from "./views/views.js"; @@ -37,7 +37,7 @@ function loadSelectedEvent() { )) { const classType = objectTypes[key]; const collection = value.collection; - classType.setup(collection, canvas); + classType.setup(collection); } copyObject(objects, currentObjects); } else { diff --git a/js/main.js b/js/main.js index bc839d64..d81ab7f7 100644 --- a/js/main.js +++ b/js/main.js @@ -16,6 +16,10 @@ const selectedObjectTypes = { "edm4hep::MCParticle", "edm4hep::ReconstructedParticle", "edm4hep::MCRecoParticleAssociation", + "edm4hep::MCRecoTrackParticleAssociation", + "edm4hep::MCRecoClusterParticleAssociation", + "edm4hep::Cluster", + "edm4hep::Track", ], }; diff --git a/js/types/links.js b/js/types/links.js index 1d83013d..524387d7 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -1,15 +1,19 @@ -export const colors = { - "daughters": "#00AA00", +const colors = { "parents": "#AA0000", + "daughters": "#00AA00", "mcreco": "#0000AA", + "tracks": "#AAAA00", + "clusters": "#00AAAA", "particles": "#AA00AA", + "mcclusters": "#AA00AA", + "mctracks": "#AA0000", }; -export function generateRandomColor() { +function generateRandomColor() { return "#" + ((0xffffff * Math.random()) << 0).toString(16).padStart(6, "0"); } -export class Link { +class Link { // we may create a specific class for each type if needed constructor(from, to) { this.from = from; @@ -109,7 +113,7 @@ export class Link { } } -export class ParentLink extends Link { +class ParentLink extends Link { constructor(from, to) { super(to, from); this.color = colors["parents"]; @@ -119,7 +123,7 @@ export class ParentLink extends Link { } } -export class DaughterLink extends Link { +class DaughterLink extends Link { constructor(from, to) { super(from, to); this.color = colors["daughters"]; @@ -129,7 +133,7 @@ export class DaughterLink extends Link { } } -export class MCRecoParticleAssociation extends Link { +class MCRecoParticleAssociation extends Link { constructor(from, to, weight) { super(from, to); this.color = colors["mcreco"]; @@ -157,19 +161,51 @@ export class MCRecoParticleAssociation extends Link { } } -export class Particles extends Link { +class Particles extends Link { constructor(from, to) { super(from, to); this.color = colors["particles"]; } } +class Clusters extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["clusters"]; + } +} + +class Tracks extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["tracks"]; + } +} + +class MCRecoTrackParticleAssociation extends Link { + constructor(from, to, weight) { + super(from, to); + this.color = colors["mctracks"]; + this.weight = weight; + } +} + +class MCRecoClusterParticleAssociation extends Link { + constructor(from, to, weight) { + super(from, to); + this.color = colors["mcclusters"]; + this.weight = weight; + } +} + export const linkTypes = { "parents": ParentLink, "daughters": DaughterLink, - "trackerHits": Link, - "startVertex": Link, - "particles": Particles, - "clusters": Link, "edm4hep::MCRecoParticleAssociation": MCRecoParticleAssociation, + "edm4hep::MCRecoClusterParticleAssociation": MCRecoClusterParticleAssociation, + "edm4hep::MCRecoTrackParticleAssociation": MCRecoTrackParticleAssociation, + "clusters": Clusters, + "tracks": Tracks, + "particles": Particles, + "startVertex": Link, }; diff --git a/js/types/load.js b/js/types/load.js index c0700bac..d2b12bd7 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -212,12 +212,3 @@ export function loadObjects(jsonData, event, objectsToLoad) { return objects; } -// console.time("load"); -// const data = loadObjects(json, 0, [ -// "edm4hep::MCParticle", -// "edm4hep::ReconstructedParticle", -// "edm4hep::Cluster", -// "edm4hep::MCRecoParticleAssociation", -// ]); -// console.timeEnd("load"); -// console.log(data); diff --git a/js/types/objects.js b/js/types/objects.js index 2196b94d..f633829c 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -6,6 +6,8 @@ class EDMObject { constructor() { this.x = NaN; this.y = NaN; + this.index = NaN; + this.collectionId = NaN; this.width = 120; this.height = 240; this.lineColor = "black"; @@ -34,81 +36,7 @@ class EDMObject { } } -export class Cluster extends EDMObject { - constructor() { - super(); - } -} - -export class ParticleID extends EDMObject { - constructor() { - super(); - } -} - -export class ReconstructedParticle extends EDMObject { - constructor() { - super(); - } - - draw(ctx) { - const boxCenterX = this.x + this.width / 2; - - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); - - const topY = this.y + 20; - const topLines = []; - topLines.push("ID: " + this.index); - const energy = parseInt(this.energy * 100) / 100; - topLines.push("e = " + energy + " GeV"); - topLines.push("c = " + this.charge + " e"); - if (Math.abs(this.charge) < 1.0 && this.charge != 0) { - if (Math.round(this.charge * 1000) === 667) { - topLines.push("q = 2/3 e"); - } - if (Math.round(this.charge * 1000) === -667) { - topLines.push("q = -2/3 e"); - } - if (Math.round(this.charge * 1000) === 333) { - topLines.push("q = 1/3 e"); - } - if (Math.round(this.charge * 1000) === -333) { - topLines.push("q = -1/3 e"); - } - } else { - topLines.push("q = " + this.charge + " e"); - } - - ctx.save(); - ctx.font = "16px sans-serif"; - for (const [i, lineText] of topLines.entries()) { - ctx.fillText( - lineText, - boxCenterX - ctx.measureText(lineText).width / 2, - topY + i * 23 - ); - } - ctx.restore(); - } - - static setup(recoCollection) {} - - static filter() {} -} - -export class Vertex extends EDMObject { - constructor() { - super(); - } -} - -export class Track extends EDMObject { - constructor() { - super(); - } -} - -export class MCParticle extends EDMObject { +class MCParticle extends EDMObject { constructor() { super(); @@ -285,11 +213,101 @@ export class MCParticle extends EDMObject { } } +class ReconstructedParticle extends EDMObject { + constructor() { + super(); + } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + + const topY = this.y + 20; + const topLines = []; + topLines.push("ID: " + this.index); + const energy = parseInt(this.energy * 100) / 100; + topLines.push("e = " + energy + " GeV"); + topLines.push("c = " + this.charge + " e"); + if (Math.abs(this.charge) < 1.0 && this.charge != 0) { + if (Math.round(this.charge * 1000) === 667) { + topLines.push("q = 2/3 e"); + } + if (Math.round(this.charge * 1000) === -667) { + topLines.push("q = -2/3 e"); + } + if (Math.round(this.charge * 1000) === 333) { + topLines.push("q = 1/3 e"); + } + if (Math.round(this.charge * 1000) === -333) { + topLines.push("q = -1/3 e"); + } + } else { + topLines.push("q = " + this.charge + " e"); + } + + ctx.save(); + ctx.font = "16px sans-serif"; + for (const [i, lineText] of topLines.entries()) { + ctx.fillText( + lineText, + boxCenterX - ctx.measureText(lineText).width / 2, + topY + i * 23 + ); + } + ctx.restore(); + } + + static setup(recoCollection) {} + + static filter() {} +} + +class Cluster extends EDMObject { + constructor() { + super(); + } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + } + + static setup(clusterCollection) {} +} + +class Track extends EDMObject { + constructor() { + super(); + } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + } + + static setup(trackCollection) {} +} + +class ParticleID extends EDMObject { + constructor() { + super(); + } +} + +class Vertex extends EDMObject { + constructor() { + super(); + } +} + export const objectTypes = { + "edm4hep::MCParticle": MCParticle, + "edm4hep::ReconstructedParticle": ReconstructedParticle, "edm4hep::Cluster": Cluster, + "edm4hep::Track": Track, "edm4hep::ParticleID": ParticleID, - "edm4hep::ReconstructedParticle": ReconstructedParticle, "edm4hep::Vertex": Vertex, - "edm4hep::Track": Track, - "edm4hep::MCParticle": MCParticle, }; diff --git a/js/views/association-view.js b/js/views/association-view.js new file mode 100644 index 00000000..998793e5 --- /dev/null +++ b/js/views/association-view.js @@ -0,0 +1,49 @@ +import { canvas } from "../main.js"; + +// List 1:1 association in a vertical list +export function buildAssociationView(viewObjects, associationName) { + const association = viewObjects.associations[associationName]; + + const fromCollection = association.map((association) => association.from); + const toCollection = association.map((association) => association.to); + + if (fromCollection.length === 0 || toCollection.length === 0) { + alert("No association found!"); + return; + } + + const fromWidth = fromCollection[0].width; + const toWidth = toCollection[0].width; + const fromHorizontalGap = 0.3 * fromWidth; + const toHorizontalGap = 0.3 * toWidth; + const gap = 2 * (fromWidth + toWidth); + const totalWidth = gap + fromWidth + toWidth; + + canvas.width = + totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + + const width = canvas.width; + + const fromHeight = fromCollection[0].height; + const toHeight = toCollection[0].height; + const fromVerticalGap = 0.3 * fromHeight; + const toVerticalGap = 0.3 * toHeight; + + const fromTotalHeight = + fromCollection.length * (fromHeight + fromVerticalGap) + fromVerticalGap; + const toTotalHeight = + toCollection.length * (toHeight + toVerticalGap) + toVerticalGap; + + canvas.height = + fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; + + for (const [index, from] of fromCollection.entries()) { + from.y = fromVerticalGap + index * (fromHeight + fromVerticalGap); + from.x = width / 2 + fromHorizontalGap; + } + + for (const [index, to] of toCollection.entries()) { + to.y = toVerticalGap + index * (toHeight + toVerticalGap); + to.x = width / 2 - toWidth - toHorizontalGap; + } +} diff --git a/js/views/clustertree.js b/js/views/clustertree.js new file mode 100644 index 00000000..f4ce1ca9 --- /dev/null +++ b/js/views/clustertree.js @@ -0,0 +1,18 @@ +import { buildTree } from "./tree.js"; +import { preFilterTree } from "./pre-filter.js"; + +export function clusterTree(viewCurrentObjects) { + const clusterCollection = + viewCurrentObjects.datatypes["edm4hep::Cluster"].collection ?? []; + + if (clusterCollection.length === 0) { + alert("No Clusters found in this event."); + return; + } + + buildTree(clusterCollection, "clusters"); +} + +export function preFilterClusterTree(currentObjects, viewObjects) { + preFilterTree(currentObjects, viewObjects, "edm4hep::Cluster", ["clusters"]); +} diff --git a/js/views/mcclusterassociation.js b/js/views/mcclusterassociation.js new file mode 100644 index 00000000..1558072c --- /dev/null +++ b/js/views/mcclusterassociation.js @@ -0,0 +1,19 @@ +import { preFilterAssociation } from "./pre-filter.js"; +import { buildAssociationView } from "./association-view.js"; + +export function mcClusterAssociation(viewObjects) { + buildAssociationView( + viewObjects, + "edm4hep::MCRecoClusterParticleAssociation" + ); +} + +export function preFilterMCCluster(currentObjects, viewObjects) { + preFilterAssociation( + currentObjects, + viewObjects, + "edm4hep::MCRecoClusterParticleAssociation", + "edm4hep::Cluster", + "edm4hep::MCParticle" + ); +} diff --git a/js/views/mcparticletree.js b/js/views/mcparticletree.js index 43d8e73d..4b702cc1 100644 --- a/js/views/mcparticletree.js +++ b/js/views/mcparticletree.js @@ -1,4 +1,5 @@ import { canvas } from "../main.js"; +import { preFilterTree } from "./pre-filter.js"; export function mcParticleTree(viewCurrentObjects) { const mcCollection = @@ -98,6 +99,9 @@ export function mcParticleTree(viewCurrentObjects) { } } -export function mcParticleTreeScroll() { - return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; +export function preFilterMCTree(currentObjects, viewObjects) { + preFilterTree(currentObjects, viewObjects, "edm4hep::MCParticle", [ + "parents", + "daughters", + ]); } diff --git a/js/views/mcrecoassociation.js b/js/views/mcrecoassociation.js index e8f19fb1..9c3b5306 100644 --- a/js/views/mcrecoassociation.js +++ b/js/views/mcrecoassociation.js @@ -1,74 +1,16 @@ -import { canvas } from "../main.js"; -import { emptyCopyObject } from "../lib/copy.js"; +import { preFilterAssociation } from "./pre-filter.js"; +import { buildAssociationView } from "./association-view.js"; export function mcRecoAssociation(viewObjects) { - const associationMCReco = - viewObjects.associations["edm4hep::MCRecoParticleAssociation"]; - - const recoCollection = associationMCReco.map( - (association) => association.from - ); - const mcCollection = associationMCReco.map((association) => association.to); - - if (mcCollection.length === 0 || recoCollection.length === 0) { - alert("No MCRecoAssociation found!"); - return; - } - - const mcWidth = mcCollection[0].width; - const recoWidth = recoCollection[0].width; - const mcHorizontalGap = 0.3 * mcWidth; - const recoHorizontalGap = 0.3 * recoWidth; - const gap = 2 * (mcWidth + recoWidth); - const totalWidth = gap + mcWidth + recoWidth; - - canvas.width = - totalWidth > window.innerWidth ? totalWidth : window.innerWidth; - - const width = canvas.width; - - const mcHeight = mcCollection[0].height; - const recoHeight = recoCollection[0].height; - const mcVerticalGap = 0.3 * mcHeight; - const recoVerticalGap = 0.3 * recoHeight; - - const mcTotalHeight = - mcCollection.length * (mcHeight + mcVerticalGap) + mcVerticalGap; - const recoTotalHeight = - recoCollection.length * (recoHeight + recoVerticalGap) + recoVerticalGap; - - canvas.height = - mcTotalHeight > recoTotalHeight ? mcTotalHeight : recoTotalHeight; - - for (const [index, mc] of mcCollection.entries()) { - mc.y = mcVerticalGap + index * (mcHeight + mcVerticalGap); - mc.x = width / 2 - mcWidth - mcHorizontalGap; - } - - for (const [index, reco] of recoCollection.entries()) { - reco.y = recoVerticalGap + index * (recoHeight + recoVerticalGap); - reco.x = width / 2 + recoHorizontalGap; - } + buildAssociationView(viewObjects, "edm4hep::MCRecoParticleAssociation"); } export function preFilterMCReco(currentObjects, viewObjects) { - emptyCopyObject(currentObjects, viewObjects); - - const associationMCReco = - currentObjects.associations["edm4hep::MCRecoParticleAssociation"]; - - const recoCollection = associationMCReco.map( - (association) => association.from + preFilterAssociation( + currentObjects, + viewObjects, + "edm4hep::MCRecoParticleAssociation", + "edm4hep::ReconstructedParticle", + "edm4hep::MCParticle" ); - const mcCollection = associationMCReco.map((association) => association.to); - - viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection = - recoCollection; - - mcCollection.forEach((mc) => { - viewObjects.datatypes["edm4hep::MCParticle"].collection.push(mc); - }); - - viewObjects.associations["edm4hep::MCRecoParticleAssociation"] = - associationMCReco; } diff --git a/js/views/mctrackassociation.js b/js/views/mctrackassociation.js new file mode 100644 index 00000000..f9d6bde0 --- /dev/null +++ b/js/views/mctrackassociation.js @@ -0,0 +1,16 @@ +import { preFilterAssociation } from "./pre-filter.js"; +import { buildAssociationView } from "./association-view.js"; + +export function mcTrackAssociation(viewObjects) { + buildAssociationView(viewObjects, "edm4hep::MCRecoTrackParticleAssociation"); +} + +export function preFilterMCTrack(currentObjects, viewObjects) { + preFilterAssociation( + currentObjects, + viewObjects, + "edm4hep::MCRecoTrackParticleAssociation", + "edm4hep::Track", + "edm4hep::MCParticle" + ); +} diff --git a/js/views/pre-filter.js b/js/views/pre-filter.js new file mode 100644 index 00000000..8334ee6b --- /dev/null +++ b/js/views/pre-filter.js @@ -0,0 +1,39 @@ +import { emptyCopyObject } from "../lib/copy.js"; + +export function preFilterAssociation( + currentObjects, + viewObjects, + associationName, + fromCollectionName, + toCollectionName +) { + emptyCopyObject(currentObjects, viewObjects); + + const association = currentObjects.associations[associationName]; + + const fromCollection = association.map((association) => association.from); + + const toCollection = association.map((association) => association.to); + + viewObjects.datatypes[fromCollectionName].collection = fromCollection; + + viewObjects.datatypes[toCollectionName].collection = toCollection; + + viewObjects.associations[associationName] = association; +} + +export function preFilterTree( + currentObjects, + viewObjects, + collectionName, + relationsNames +) { + emptyCopyObject(currentObjects, viewObjects); + viewObjects.datatypes[collectionName].collection = + currentObjects.datatypes[collectionName].collection; + + relationsNames.forEach((relationName) => { + viewObjects.datatypes[collectionName].oneToMany[relationName] = + currentObjects.datatypes[collectionName].oneToMany[relationName]; + }); +} diff --git a/js/views/recoparticletree.js b/js/views/recoparticletree.js index e72b3c96..26a73e32 100644 --- a/js/views/recoparticletree.js +++ b/js/views/recoparticletree.js @@ -1,4 +1,5 @@ -import { canvas } from "../main.js"; +import { buildTree } from "./tree.js"; +import { preFilterTree } from "./pre-filter.js"; export function recoParticleTree(viewCurrentObjects) { const recoCollection = @@ -9,82 +10,11 @@ export function recoParticleTree(viewCurrentObjects) { alert("No ReconstructedParticles found in this event."); } - const nodes = new Set(); - const children = new Set(); - - for (const recoParticle of recoCollection) { - const particles = recoParticle.oneToManyRelations["particles"].map( - (link) => link.to - ); - nodes.add(`${recoParticle.index}-${recoParticle.collectionId}`); - for (const recoParticleChild of particles) { - children.add( - `${recoParticleChild.index}-${recoParticleChild.collectionId}` - ); - } - } - - const rootNodesIds = nodes.difference(children); - const rootNodes = []; - - recoCollection.forEach((recoParticle) => { - if ( - rootNodesIds.has(`${recoParticle.index}-${recoParticle.collectionId}`) - ) { - rootNodes.push(recoParticle); - } - }); - - rootNodes.forEach((rootNode) => { - const stack = [[rootNode, 0]]; - - while (stack.length > 0) { - const [node, row] = stack.pop(); - const id = `${node.index}-${node.collectionId}`; - if (nodes.has(id)) { - nodes.delete(id); - node.row = row; - - const particles = node.oneToManyRelations["particles"]; - - particles.forEach((link) => { - stack.push([link.to, row + 1]); - }); - } - } - }); - - const horizontalGap = recoCollection[0].width * 0.4; - const verticalGap = recoCollection[0].height * 0.3; - const boxWidth = recoCollection[0].width; - const boxHeight = recoCollection[0].height; - - const matrix = []; - - recoCollection.forEach((recoParticle) => { - const row = recoParticle.row; - if (matrix[row] === undefined) { - matrix[row] = []; - } - matrix[row].push(recoParticle); - }); - - matrix.forEach((row, i) => { - row.forEach((recoParticle, j) => { - recoParticle.x = j * horizontalGap + j * boxWidth + horizontalGap; - recoParticle.y = i * verticalGap + i * boxHeight + verticalGap; - }); - }); - - canvas.width = - boxWidth * matrix[0].length + horizontalGap * (matrix[0].length + 1); - canvas.height = - boxHeight * (matrix.length + 1) + verticalGap * (matrix.length + 2); + buildTree(recoCollection, "particles"); } -export function recoParticleTreeScroll() { - return { - x: 0, - y: 0, - }; +export function preFilterRecoTree(currentObjects, viewObjects) { + preFilterTree(currentObjects, viewObjects, "edm4hep::ReconstructedParticle", [ + "particles", + ]); } diff --git a/js/views/scrolls.js b/js/views/scrolls.js new file mode 100644 index 00000000..3624add2 --- /dev/null +++ b/js/views/scrolls.js @@ -0,0 +1,9 @@ +import { canvas } from "../main.js"; + +export function scrollTopCenter() { + return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; +} + +export function scrollTopLeft() { + return { x: 0, y: 0 }; +} diff --git a/js/views/tracktree.js b/js/views/tracktree.js new file mode 100644 index 00000000..523441b8 --- /dev/null +++ b/js/views/tracktree.js @@ -0,0 +1,18 @@ +import { buildTree } from "./tree.js"; +import { preFilterTree } from "./pre-filter.js"; + +export function trackTree(viewCurrentObjects) { + const trackCollection = + viewCurrentObjects.datatypes["edm4hep::Track"].collection ?? []; + + if (trackCollection.length === 0) { + alert("No Tracks found in this event."); + return; + } + + buildTree(trackCollection, "tracks"); +} + +export function preFilterTrackTree(currentObjects, viewObjects) { + preFilterTree(currentObjects, viewObjects, "edm4hep::Track", ["tracks"]); +} diff --git a/js/views/tree.js b/js/views/tree.js new file mode 100644 index 00000000..e57f6ffc --- /dev/null +++ b/js/views/tree.js @@ -0,0 +1,72 @@ +import { canvas } from "../main.js"; + +// All particles that are related to itself have an one to many relation +export function buildTree(collection, relationOfReference) { + const nodes = new Set(); + const children = new Set(); + + for (const object of collection) { + const objects = object.oneToManyRelations[relationOfReference].map( + (link) => link.to + ); + nodes.add(`${object.index}-${object.collectionId}`); + for (const childObject of objects) { + children.add(`${childObject.index}-${childObject.collectionId}`); + } + } + + const rootNodesIds = nodes.difference(children); + const rootNodes = []; + + collection.forEach((object) => { + if (rootNodesIds.has(`${object.index}-${object.collectionId}`)) { + rootNodes.push(object); + } + }); + + rootNodes.forEach((rootNode) => { + const stack = [[rootNode, 0]]; + + while (stack.length > 0) { + const [node, row] = stack.pop(); + const id = `${node.index}-${node.collectionId}`; + if (nodes.has(id)) { + nodes.delete(id); + node.row = row; + + const childObjectLinks = node.oneToManyRelations[relationOfReference]; + + childObjectLinks.forEach((link) => { + stack.push([link.to, row + 1]); + }); + } + } + }); + + const horizontalGap = collection[0].width * 0.4; + const verticalGap = collection[0].height * 0.3; + const boxWidth = collection[0].width; + const boxHeight = collection[0].height; + + const matrix = []; + + collection.forEach((object) => { + const row = object.row; + if (matrix[row] === undefined) { + matrix[row] = []; + } + matrix[row].push(object); + }); + + matrix.forEach((row, i) => { + row.forEach((object, j) => { + object.x = j * horizontalGap + j * boxWidth + horizontalGap; + object.y = i * verticalGap + i * boxHeight + verticalGap; + }); + }); + + canvas.width = + boxWidth * matrix[0].length + horizontalGap * (matrix[0].length + 1); + canvas.height = + boxHeight * (matrix.length + 1) + verticalGap * (matrix.length + 2); +} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 86d00b70..a5783d91 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -1,40 +1,69 @@ -import { mcParticleTree, mcParticleTreeScroll } from "./mcparticletree.js"; +import { mcParticleTree, preFilterMCTree } from "./mcparticletree.js"; import { mcRecoAssociation, preFilterMCReco } from "./mcrecoassociation.js"; -import { - recoParticleTree, - recoParticleTreeScroll, -} from "./recoparticletree.js"; +import { recoParticleTree, preFilterRecoTree } from "./recoparticletree.js"; import { setupMCParticleFilter } from "../filter/mcparticle.js"; +import { trackTree, preFilterTrackTree } from "./tracktree.js"; +import { clusterTree, preFilterClusterTree } from "./clustertree.js"; +import { scrollTopCenter, scrollTopLeft } from "./scrolls.js"; +import { preFilterMCTrack, mcTrackAssociation } from "./mctrackassociation.js"; +import { + preFilterMCCluster, + mcClusterAssociation, +} from "./mcclusterassociation.js"; export const views = { "Monte Carlo Particle Tree": { filters: setupMCParticleFilter, viewFunction: mcParticleTree, - scrollFunction: mcParticleTreeScroll, - preFilterFunction: (currentObjects, viewObjects) => { - viewObjects.datatypes = {}; - viewObjects.associations = {}; - viewObjects.datatypes["edm4hep::MCParticle"] = - currentObjects.datatypes["edm4hep::MCParticle"]; - }, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterMCTree, }, "Reconstructed Particle Tree": { filters: () => {}, viewFunction: recoParticleTree, - scrollFunction: recoParticleTreeScroll, - preFilterFunction: (currentObjects, viewObjects) => { - viewObjects.datatypes = {}; - viewObjects.associations = {}; - viewObjects.datatypes["edm4hep::ReconstructedParticle"] = - currentObjects.datatypes["edm4hep::ReconstructedParticle"]; - }, + scrollFunction: scrollTopLeft, + preFilterFunction: preFilterRecoTree, + }, + "Track Tree": { + filters: () => {}, + viewFunction: trackTree, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterTrackTree, + }, + "Cluster Tree": { + filters: () => {}, + viewFunction: clusterTree, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterClusterTree, + }, + "Reconstructed Particle-Cluster": { + filters: () => {}, + viewFunction: () => {}, + scrollFunction: () => {}, + preFilterFunction: () => {}, + }, + "Reconstructed Particle-Track": { + filters: () => {}, + viewFunction: () => {}, + scrollFunction: () => {}, + preFilterFunction: () => {}, }, "Monte Carlo-Reconstructed Particle": { filters: () => {}, viewFunction: mcRecoAssociation, - scrollFunction: () => { - return { x: (canvas.width - window.innerWidth) / 2, y: 0 }; - }, + scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCReco, }, + "Monte Carlo Particle-Track": { + filters: () => {}, + viewFunction: mcTrackAssociation, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterMCTrack, + }, + "Monte Carlo Particle-Cluster": { + filters: () => {}, + viewFunction: mcClusterAssociation, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterMCCluster, + }, }; diff --git a/model/index.js b/model/index.js index cfd58d44..5db3966f 100644 --- a/model/index.js +++ b/model/index.js @@ -18,6 +18,8 @@ const configTypes = new Set([ "edm4hep::ReconstructedParticle", "edm4hep::Track", "edm4hep::MCRecoParticleAssociation", + "edm4hep::MCRecoTrackParticleAssociation", + "edm4hep::MCRecoClusterParticleAssociation", ]); const selectedTypes = Object.entries(datatypes).filter(([key, _]) => diff --git a/output/datatypes.js b/output/datatypes.js index bbd0c3e0..31826a96 100644 --- a/output/datatypes.js +++ b/output/datatypes.js @@ -247,5 +247,39 @@ export const datatypes = { "name": "sim" } ] + }, + "edm4hep::MCRecoClusterParticleAssociation": { + "members": [ + { + "name": "weight" + } + ], + "oneToOneRelations": [ + { + "type": "edm4hep::Cluster", + "name": "rec" + }, + { + "type": "edm4hep::MCParticle", + "name": "sim" + } + ] + }, + "edm4hep::MCRecoTrackParticleAssociation": { + "members": [ + { + "name": "weight" + } + ], + "oneToOneRelations": [ + { + "type": "edm4hep::Track", + "name": "rec" + }, + { + "type": "edm4hep::MCParticle", + "name": "sim" + } + ] } } \ No newline at end of file From c8c2caf7c83014b26c437130271c054917c777b8 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 2 Jul 2024 21:07:30 -0500 Subject: [PATCH 16/60] fix exports for tests --- js/types/links.js | 2 +- js/types/objects.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/types/links.js b/js/types/links.js index 524387d7..4934a72d 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -13,7 +13,7 @@ function generateRandomColor() { return "#" + ((0xffffff * Math.random()) << 0).toString(16).padStart(6, "0"); } -class Link { +export class Link { // we may create a specific class for each type if needed constructor(from, to) { this.from = from; diff --git a/js/types/objects.js b/js/types/objects.js index f633829c..6f05ea37 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -36,7 +36,7 @@ class EDMObject { } } -class MCParticle extends EDMObject { +export class MCParticle extends EDMObject { constructor() { super(); From 3bf0b3eba857ab13fdcc0f0f1c3961605ad803fc Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Wed, 3 Jul 2024 21:49:42 -0500 Subject: [PATCH 17/60] add views for 1:many relations and draw different objects with different objects/colors and its properties --- js/graphic-primitives.js | 59 ------------ js/lib/graphic-primitives.js | 109 +++++++++++++++++++++ js/{tools.js => lib/messages.js} | 0 js/lib/parseCharge.js | 18 ++++ js/main.js | 2 +- js/types/links.js | 96 +++---------------- js/types/objects.js | 156 +++++++++++++++++-------------- js/views/association-view.js | 6 +- js/views/oneToMany.js | 71 ++++++++++++++ js/views/pre-filter.js | 21 +++++ js/views/recocluster.js | 21 +++++ js/views/recotrack.js | 21 +++++ js/views/tree.js | 11 ++- js/views/views-dictionary.js | 14 +-- test/primitives.test.js | 4 +- test/tools.test.js | 2 +- 16 files changed, 388 insertions(+), 223 deletions(-) delete mode 100644 js/graphic-primitives.js create mode 100644 js/lib/graphic-primitives.js rename js/{tools.js => lib/messages.js} (100%) create mode 100644 js/lib/parseCharge.js create mode 100644 js/views/oneToMany.js create mode 100644 js/views/recocluster.js create mode 100644 js/views/recotrack.js diff --git a/js/graphic-primitives.js b/js/graphic-primitives.js deleted file mode 100644 index e9aec198..00000000 --- a/js/graphic-primitives.js +++ /dev/null @@ -1,59 +0,0 @@ -export function drawLine(ctx, startX, startY, endX, endY, color) { - ctx.save(); - ctx.strokeStyle = color; - ctx.beginPath(); - ctx.moveTo(startX, startY); - ctx.lineTo(endX, endY); - ctx.stroke(); - ctx.restore(); -} - -export function drawCross(ctx, x, y, color = "#F00") { - const crossLenght = 6; - ctx.save(); - ctx.strokeStyle = color; - ctx.beginPath(); - ctx.moveTo(x - crossLenght, y - crossLenght); - ctx.lineTo(x + crossLenght, y + crossLenght); - ctx.stroke(); - ctx.beginPath(); - ctx.moveTo(x + crossLenght, y - crossLenght); - ctx.lineTo(x - crossLenght, y + crossLenght); - ctx.stroke(); - ctx.restore(); -} - -export function drawRoundedRect(ctx, x, y, width, height, fillColor) { - ctx.save(); - - ctx.fillStyle = fillColor; - ctx.beginPath(); - ctx.roundRect(x, y, width, height, 15); - ctx.fill(); - - ctx.strokeStyle = "black"; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.roundRect(x, y, width, height, 15); - ctx.stroke(); - ctx.restore(); -} - -export function drawTex(ctx, x, y, texImg, maxWidth) { - let scale = (maxWidth * 0.9) / texImg.naturalWidth; - if (scale > 2) { - scale = 2; - } - const tempHeight = texImg.naturalHeight * scale; - const tempWidth = texImg.naturalWidth * scale; - - ctx.save(); - ctx.drawImage( - texImg, - x - tempWidth / 2, - y - tempHeight / 2, - tempWidth, - tempHeight - ); - ctx.restore(); -} diff --git a/js/lib/graphic-primitives.js b/js/lib/graphic-primitives.js new file mode 100644 index 00000000..2e3438f5 --- /dev/null +++ b/js/lib/graphic-primitives.js @@ -0,0 +1,109 @@ +export function drawRoundedRect(ctx, x, y, width, height, fillColor, radius) { + ctx.save(); + + ctx.fillStyle = fillColor; + ctx.beginPath(); + ctx.roundRect(x, y, width, height, radius); + ctx.fill(); + + ctx.strokeStyle = "black"; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.roundRect(x, y, width, height, radius); + ctx.stroke(); + ctx.restore(); +} + +export function drawTex(ctx, x, y, texImg, maxWidth) { + let scale = (maxWidth * 0.9) / texImg.naturalWidth; + if (scale > 2) { + scale = 2; + } + const tempHeight = texImg.naturalHeight * scale; + const tempWidth = texImg.naturalWidth * scale; + + ctx.save(); + ctx.drawImage( + texImg, + x - tempWidth / 2, + y - tempHeight / 2, + tempWidth, + tempHeight + ); + ctx.restore(); +} + +export function drawTextLines(ctx, lines, boxCenterX, y, n) { + ctx.save(); + ctx.font = "16px sans-serif"; + for (const [i, lineText] of lines.entries()) { + ctx.fillText( + lineText, + boxCenterX - ctx.measureText(lineText).width / 2, + y + i * n + ); + } + ctx.restore(); +} + +export function drawBezierLink(ctx, link) { + const boxFrom = link.from; + const boxTo = link.to; + + const fromX = boxFrom.x + boxFrom.width / 2; + const fromY = boxFrom.y + boxFrom.height; + const toX = boxTo.x + boxTo.width / 2; + const toY = boxTo.y; + + if (toY > fromY) { + var cpFromY = (toY - fromY) / 2 + fromY; + var cpToY = cpFromY; + } else { + cpFromY = (fromY - toY) / 2 + fromY; + cpToY = toY - (fromY - toY) / 2; + } + + if (toX > fromX) { + var cpFromX = (toX - fromX) / 4 + fromX; + var cpToX = (3 * (toX - fromX)) / 4 + fromX; + } else { + cpFromX = (3 * (fromX - toX)) / 4 + toX; + cpToX = (fromX - toX) / 4 + toX; + } + + ctx.save(); + ctx.strokeStyle = link.color; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(fromX + link.xShift, fromY); + ctx.bezierCurveTo( + cpFromX + link.xShift, + cpFromY, + cpToX + link.xShift, + cpToY, + toX + link.xShift, + toY + ); + ctx.stroke(); + ctx.restore(); +} + +export function drawStraightLink(ctx, link) { + const boxFrom = link.from; + const boxTo = link.to; + + const fromX = boxFrom.x + boxFrom.width / 2; + const fromY = boxFrom.y + boxFrom.height / 2; + + const toX = boxTo.x + boxTo.width / 2; + const toY = boxTo.y + boxTo.height / 2; + + ctx.save(); + ctx.strokeStyle = link.color; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(fromX, fromY); + ctx.lineTo(toX, toY); + ctx.stroke(); + ctx.restore(); +} diff --git a/js/tools.js b/js/lib/messages.js similarity index 100% rename from js/tools.js rename to js/lib/messages.js diff --git a/js/lib/parseCharge.js b/js/lib/parseCharge.js new file mode 100644 index 00000000..306a2039 --- /dev/null +++ b/js/lib/parseCharge.js @@ -0,0 +1,18 @@ +export function parseCharge(charge) { + if (Math.abs(charge) < 1.0 && charge != 0) { + if (Math.round(charge * 1000) === 667) { + return "q = 2/3 e"; + } + if (Math.round(charge * 1000) === -667) { + return "q = -2/3 e"; + } + if (Math.round(charge * 1000) === 333) { + return "q = 1/3 e"; + } + if (Math.round(charge * 1000) === -333) { + return "q = -1/3 e"; + } + } else { + return "q = " + charge + " e"; + } +} diff --git a/js/main.js b/js/main.js index d81ab7f7..192fcd09 100644 --- a/js/main.js +++ b/js/main.js @@ -1,4 +1,4 @@ -import { errorMsg } from "./tools.js"; +import { errorMsg } from "./lib/messages.js"; import { renderEvent } from "./event-number.js"; import { setView, getView } from "./views/views.js"; import { views } from "./views/views-dictionary.js"; diff --git a/js/types/links.js b/js/types/links.js index 4934a72d..fa19e997 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -1,3 +1,5 @@ +import { drawBezierLink, drawStraightLink } from "../lib/graphic-primitives.js"; + const colors = { "parents": "#AA0000", "daughters": "#00AA00", @@ -5,16 +7,11 @@ const colors = { "tracks": "#AAAA00", "clusters": "#00AAAA", "particles": "#AA00AA", - "mcclusters": "#AA00AA", - "mctracks": "#AA0000", + "mcclusters": "#D8F1A0", + "mctracks": "#fe5e41", }; -function generateRandomColor() { - return "#" + ((0xffffff * Math.random()) << 0).toString(16).padStart(6, "0"); -} - export class Link { - // we may create a specific class for each type if needed constructor(from, to) { this.from = from; this.to = to; @@ -23,64 +20,7 @@ export class Link { } draw(ctx) { - const boxFrom = this.from; - const boxTo = this.to; - - const fromX = boxFrom.x + boxFrom.width / 2; - const fromY = boxFrom.y + boxFrom.height; - const toX = boxTo.x + boxTo.width / 2; - const toY = boxTo.y; - - if (toY > fromY) { - var cpFromY = (toY - fromY) / 2 + fromY; - var cpToY = cpFromY; - } else { - cpFromY = (fromY - toY) / 2 + fromY; - cpToY = toY - (fromY - toY) / 2; - } - - if (toX > fromX) { - var cpFromX = (toX - fromX) / 4 + fromX; - var cpToX = (3 * (toX - fromX)) / 4 + fromX; - } else { - cpFromX = (3 * (fromX - toX)) / 4 + toX; - cpToX = (fromX - toX) / 4 + toX; - } - - /* - drawCross(ctx, fromX, fromY, "blue"); - drawCross(ctx, toX, toY, "green"); - drawCross(ctx, cpFromX, cpFromY, "yellow"); - drawLine(ctx, fromX, fromY, cpFromX, cpFromY, "yellow") - drawCross(ctx, cpToX, cpToY, "orange"); - drawLine(ctx, toX, toY, cpToX, cpToY, "orange") - */ - - ctx.save(); - ctx.strokeStyle = this.color; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(fromX + this.xShift, fromY); - ctx.bezierCurveTo( - cpFromX + this.xShift, - cpFromY, - cpToX + this.xShift, - cpToY, - toX + this.xShift, - toY - ); - ctx.stroke(); - ctx.restore(); - - /* - ctx.save(); - ctx.font = "14px sans-serif"; - ctx.fillStyle = this.color; - const idText = "ID: " + this.id; - ctx.fillText(idText, - cpToX, cpToY); - ctx.restore(); - */ + drawBezierLink(ctx, this); } isVisible(x, y, width, height) { @@ -141,23 +81,7 @@ class MCRecoParticleAssociation extends Link { } draw(ctx) { - const boxFrom = this.from; - const boxTo = this.to; - - const fromX = boxFrom.x + boxFrom.width / 2; - const fromY = boxFrom.y + boxFrom.height / 2; - - const toX = boxTo.x + boxTo.width / 2; - const toY = boxTo.y + boxTo.height / 2; - - ctx.save(); - ctx.strokeStyle = this.color; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(fromX, fromY); - ctx.lineTo(toX, toY); - ctx.stroke(); - ctx.restore(); + drawStraightLink(ctx, this); } } @@ -188,6 +112,10 @@ class MCRecoTrackParticleAssociation extends Link { this.color = colors["mctracks"]; this.weight = weight; } + + draw(ctx) { + drawStraightLink(ctx, this); + } } class MCRecoClusterParticleAssociation extends Link { @@ -196,6 +124,10 @@ class MCRecoClusterParticleAssociation extends Link { this.color = colors["mcclusters"]; this.weight = weight; } + + draw(ctx) { + drawStraightLink(ctx, this); + } } export const linkTypes = { diff --git a/js/types/objects.js b/js/types/objects.js index 6f05ea37..28d0ed52 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -1,6 +1,11 @@ -import { drawTex, drawRoundedRect } from "../graphic-primitives.js"; +import { + drawTex, + drawRoundedRect, + drawTextLines, +} from "../lib/graphic-primitives.js"; import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; +import { parseCharge } from "../lib/parseCharge.js"; class EDMObject { constructor() { @@ -60,7 +65,15 @@ export class MCParticle extends EDMObject { draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#dff6ff", + 15 + ); if (this.texImg.complete) { drawTex( @@ -94,41 +107,11 @@ export class MCParticle extends EDMObject { bottomLines.push("d = " + this.vertex + " mm"); bottomLines.push("t = " + this.time + " ns"); bottomLines.push("m = " + this.mass + " GeV"); - if (Math.abs(this.charge) < 1.0 && this.charge != 0) { - if (Math.round(this.charge * 1000) === 667) { - bottomLines.push("q = 2/3 e"); - } - if (Math.round(this.charge * 1000) === -667) { - bottomLines.push("q = -2/3 e"); - } - if (Math.round(this.charge * 1000) === 333) { - bottomLines.push("q = 1/3 e"); - } - if (Math.round(this.charge * 1000) === -333) { - bottomLines.push("q = -1/3 e"); - } - } else { - bottomLines.push("q = " + this.charge + " e"); - } + bottomLines.push(parseCharge(this.charge)); - ctx.save(); - ctx.font = "16px sans-serif"; - for (const [i, lineText] of topLines.entries()) { - ctx.fillText( - lineText, - boxCenterX - ctx.measureText(lineText).width / 2, - topY + i * 23 - ); - } + drawTextLines(ctx, topLines, boxCenterX, topY, 23); - for (const [i, lineText] of bottomLines.entries()) { - ctx.fillText( - lineText, - boxCenterX - ctx.measureText(lineText).width / 2, - bottomY + i * 22 - ); - } - ctx.restore(); + drawTextLines(ctx, bottomLines, boxCenterX, bottomY, 22); } static setup(mcCollection) { @@ -216,46 +199,32 @@ export class MCParticle extends EDMObject { class ReconstructedParticle extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 180; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#fbffdf", + 30 + ); const topY = this.y + 20; - const topLines = []; - topLines.push("ID: " + this.index); + const lines = []; + lines.push("ID: " + this.index); const energy = parseInt(this.energy * 100) / 100; - topLines.push("e = " + energy + " GeV"); - topLines.push("c = " + this.charge + " e"); - if (Math.abs(this.charge) < 1.0 && this.charge != 0) { - if (Math.round(this.charge * 1000) === 667) { - topLines.push("q = 2/3 e"); - } - if (Math.round(this.charge * 1000) === -667) { - topLines.push("q = -2/3 e"); - } - if (Math.round(this.charge * 1000) === 333) { - topLines.push("q = 1/3 e"); - } - if (Math.round(this.charge * 1000) === -333) { - topLines.push("q = -1/3 e"); - } - } else { - topLines.push("q = " + this.charge + " e"); - } + lines.push("e = " + energy + " GeV"); + lines.push(parseCharge(this.charge)); + lines.push("pdg = " + this.PDG); - ctx.save(); - ctx.font = "16px sans-serif"; - for (const [i, lineText] of topLines.entries()) { - ctx.fillText( - lineText, - boxCenterX - ctx.measureText(lineText).width / 2, - topY + i * 23 - ); - } - ctx.restore(); + drawTextLines(ctx, lines, boxCenterX, topY, 23); } static setup(recoCollection) {} @@ -266,12 +235,37 @@ class ReconstructedParticle extends EDMObject { class Cluster extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 180; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#ffe8df", + 20 + ); + + const topY = this.y + 20; + const lines = []; + lines.push("ID: " + this.index); + lines.push("type: " + this.type); + const energy = parseInt(this.energy * 100) / 100; + lines.push("e = " + energy + " GeV"); + const x = parseInt(this.position.x * 100) / 100; + const y = parseInt(this.position.y * 100) / 100; + const z = parseInt(this.position.z * 100) / 100; + lines.push(`pos = (${x},`); + lines.push(`${y},`); + lines.push(`${z}) mm`); + + drawTextLines(ctx, lines, boxCenterX, topY, 23); } static setup(clusterCollection) {} @@ -280,12 +274,38 @@ class Cluster extends EDMObject { class Track extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 180; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect(ctx, this.x, this.y, this.width, this.height, "#f5f5f5"); + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#fff6df", + 25 + ); + + const topY = this.y + 20; + + const lines = []; + lines.push("ID: " + this.index); + lines.push("type: " + this.type); + const chi2 = parseInt(this.chi2 * 100) / 100; + const ndf = parseInt(this.ndf * 100) / 100; + const chiNdf = `${chi2} / ${ndf}`; + lines.push("chi2/ndf = " + chiNdf); + lines.push("dEdx = " + this.dEdx); + + const trackerHitsCount = this.oneToManyRelations["trackerHits"].length; + lines.push("tracker hits: " + trackerHitsCount); + + drawTextLines(ctx, lines, boxCenterX, topY, 23); } static setup(trackCollection) {} diff --git a/js/views/association-view.js b/js/views/association-view.js index 998793e5..92bcd3ff 100644 --- a/js/views/association-view.js +++ b/js/views/association-view.js @@ -37,13 +37,15 @@ export function buildAssociationView(viewObjects, associationName) { canvas.height = fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; + const fromX = width / 2 - fromWidth - fromHorizontalGap; for (const [index, from] of fromCollection.entries()) { from.y = fromVerticalGap + index * (fromHeight + fromVerticalGap); - from.x = width / 2 + fromHorizontalGap; + from.x = fromX; } + const toX = width / 2 + toHorizontalGap; for (const [index, to] of toCollection.entries()) { to.y = toVerticalGap + index * (toHeight + toVerticalGap); - to.x = width / 2 - toWidth - toHorizontalGap; + to.x = toX; } } diff --git a/js/views/oneToMany.js b/js/views/oneToMany.js new file mode 100644 index 00000000..07cd179e --- /dev/null +++ b/js/views/oneToMany.js @@ -0,0 +1,71 @@ +import { canvas } from "../main.js"; + +// One to Many relation between two collections +export function oneToManyView( + viewCurrentObjects, + fromCollectionName, + toCollectionName, + relationName +) { + const fromCollection = + viewCurrentObjects.datatypes[fromCollectionName].collection; + + const toCollection = + viewCurrentObjects.datatypes[toCollectionName].collection; + + if (fromCollection.length === 0 || toCollection.length === 0) { + alert("No association found!"); + return; + } + + const fromWidth = fromCollection[0].width; + const fromHeight = fromCollection[0].height; + const fromVerticalGap = 0.3 * fromHeight; + const fromHorizontalGap = 0.3 * fromWidth; + + const toWidth = toCollection[0].width; + const toHeight = toCollection[0].height; + const toVerticalGap = 0.3 * toHeight; + const toHorizontalGap = 0.3 * toWidth; + + const gap = 2 * (fromWidth + toWidth); + const totalWidth = gap + fromWidth + toWidth; + + const fromTotalHeight = + fromCollection.length * (fromHeight + fromVerticalGap) + fromVerticalGap; + + const toTotalHeight = + toCollection.length * (toHeight + toVerticalGap) + toVerticalGap; + + canvas.height = + fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; + + canvas.width = + totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + + const width = canvas.width; + + const fromX = width / 2 - fromWidth - fromHorizontalGap; + const toX = width / 2 + toHorizontalGap; + + let toCount = 0; + + fromCollection.forEach((fromObject) => { + const objectRelations = fromObject.oneToManyRelations[relationName]; + const length = objectRelations.length; + + objectRelations.forEach((relation) => { + const toObject = relation.to; + + toObject.x = toX; + toObject.y = toVerticalGap + toCount * (toHeight + toVerticalGap); + toCount++; + }); + + fromObject.x = fromX; + const firstY = objectRelations[0].to.y; + const lastY = objectRelations[length - 1].to.y; + + fromObject.y = parseInt((firstY + lastY) / 2); + }); +} diff --git a/js/views/pre-filter.js b/js/views/pre-filter.js index 8334ee6b..af2a4fd1 100644 --- a/js/views/pre-filter.js +++ b/js/views/pre-filter.js @@ -37,3 +37,24 @@ export function preFilterTree( currentObjects.datatypes[collectionName].oneToMany[relationName]; }); } + +export function preFilterOneToMany( + currentObjects, + viewObjects, + fromCollectionName, + toCollectionName, + relationName +) { + emptyCopyObject(currentObjects, viewObjects); + + const relations = + currentObjects.datatypes[fromCollectionName].oneToMany[relationName]; + + viewObjects.datatypes[fromCollectionName].oneToMany[relationName] = relations; + + const fromCollection = relations.map((relation) => relation.from); + const toCollection = relations.map((relation) => relation.to); + + viewObjects.datatypes[fromCollectionName].collection = fromCollection; + viewObjects.datatypes[toCollectionName].collection = toCollection; +} diff --git a/js/views/recocluster.js b/js/views/recocluster.js new file mode 100644 index 00000000..72b92e17 --- /dev/null +++ b/js/views/recocluster.js @@ -0,0 +1,21 @@ +import { preFilterOneToMany } from "./pre-filter.js"; +import { oneToManyView } from "./oneToMany.js"; + +export function recoClusterView(viewCurrentObjects) { + oneToManyView( + viewCurrentObjects, + "edm4hep::ReconstructedParticle", + "edm4hep::Cluster", + "clusters" + ); +} + +export function preFilterRecoCluster(currentObjects, viewObjects) { + preFilterOneToMany( + currentObjects, + viewObjects, + "edm4hep::ReconstructedParticle", + "edm4hep::Cluster", + "clusters" + ); +} diff --git a/js/views/recotrack.js b/js/views/recotrack.js new file mode 100644 index 00000000..39b1793a --- /dev/null +++ b/js/views/recotrack.js @@ -0,0 +1,21 @@ +import { preFilterOneToMany } from "./pre-filter.js"; +import { oneToManyView } from "./oneToMany.js"; + +export function recoTrackView(viewCurrentObjects) { + oneToManyView( + viewCurrentObjects, + "edm4hep::ReconstructedParticle", + "edm4hep::Track", + "tracks" + ); +} + +export function preFilterRecoTrack(currentObjects, viewObjects) { + preFilterOneToMany( + currentObjects, + viewObjects, + "edm4hep::ReconstructedParticle", + "edm4hep::Track", + "tracks" + ); +} diff --git a/js/views/tree.js b/js/views/tree.js index e57f6ffc..bfe89342 100644 --- a/js/views/tree.js +++ b/js/views/tree.js @@ -65,8 +65,15 @@ export function buildTree(collection, relationOfReference) { }); }); - canvas.width = + const totalWidth = boxWidth * matrix[0].length + horizontalGap * (matrix[0].length + 1); - canvas.height = + + canvas.width = + totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + + const totalHeight = boxHeight * (matrix.length + 1) + verticalGap * (matrix.length + 2); + + canvas.height = + totalHeight > window.innerHeight ? totalHeight : window.innerHeight; } diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index a5783d91..df43922b 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -10,6 +10,8 @@ import { preFilterMCCluster, mcClusterAssociation, } from "./mcclusterassociation.js"; +import { recoTrackView, preFilterRecoTrack } from "./recotrack.js"; +import { recoClusterView, preFilterRecoCluster } from "./recocluster.js"; export const views = { "Monte Carlo Particle Tree": { @@ -38,15 +40,15 @@ export const views = { }, "Reconstructed Particle-Cluster": { filters: () => {}, - viewFunction: () => {}, - scrollFunction: () => {}, - preFilterFunction: () => {}, + viewFunction: recoClusterView, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterRecoCluster, }, "Reconstructed Particle-Track": { filters: () => {}, - viewFunction: () => {}, - scrollFunction: () => {}, - preFilterFunction: () => {}, + viewFunction: recoTrackView, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterRecoTrack, }, "Monte Carlo-Reconstructed Particle": { filters: () => {}, diff --git a/test/primitives.test.js b/test/primitives.test.js index f58cb271..550f9999 100644 --- a/test/primitives.test.js +++ b/test/primitives.test.js @@ -1,5 +1,5 @@ import { jest } from "@jest/globals"; -import { drawRoundedRect, drawTex } from "../js/graphic-primitives.js"; +import { drawRoundedRect, drawTex } from "../js/lib/graphic-primitives.js"; let ctx; @@ -24,7 +24,7 @@ afterEach(() => { describe("drawRoundedRect", () => { it("should draw a rounded rectangle with the correct properties", () => { - drawRoundedRect(ctx, 10, 20, 100, 200, "red"); + drawRoundedRect(ctx, 10, 20, 100, 200, "red", 15); expect(ctx.save).toHaveBeenCalled(); expect(ctx.fillStyle).toBe("red"); diff --git a/test/tools.test.js b/test/tools.test.js index 84cdaa98..7a0ad3f1 100644 --- a/test/tools.test.js +++ b/test/tools.test.js @@ -1,4 +1,4 @@ -import { infoMsg, errorMsg } from "../js/tools"; +import { infoMsg, errorMsg } from "../js/lib/messages.js"; let msgDiv; From a617726339daaa31233c1e347525d9776a0604e7 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 6 Jul 2024 19:18:29 -0500 Subject: [PATCH 18/60] merge reco particle view with cluster and tracks --- js/views/oneToMany.js | 71 ---------------- js/views/pre-filter.js | 21 ----- js/views/recocluster.js | 21 ----- js/views/recoclustertrack.js | 154 +++++++++++++++++++++++++++++++++++ js/views/recotrack.js | 21 ----- js/views/views-dictionary.js | 18 ++-- 6 files changed, 161 insertions(+), 145 deletions(-) delete mode 100644 js/views/oneToMany.js delete mode 100644 js/views/recocluster.js create mode 100644 js/views/recoclustertrack.js delete mode 100644 js/views/recotrack.js diff --git a/js/views/oneToMany.js b/js/views/oneToMany.js deleted file mode 100644 index 07cd179e..00000000 --- a/js/views/oneToMany.js +++ /dev/null @@ -1,71 +0,0 @@ -import { canvas } from "../main.js"; - -// One to Many relation between two collections -export function oneToManyView( - viewCurrentObjects, - fromCollectionName, - toCollectionName, - relationName -) { - const fromCollection = - viewCurrentObjects.datatypes[fromCollectionName].collection; - - const toCollection = - viewCurrentObjects.datatypes[toCollectionName].collection; - - if (fromCollection.length === 0 || toCollection.length === 0) { - alert("No association found!"); - return; - } - - const fromWidth = fromCollection[0].width; - const fromHeight = fromCollection[0].height; - const fromVerticalGap = 0.3 * fromHeight; - const fromHorizontalGap = 0.3 * fromWidth; - - const toWidth = toCollection[0].width; - const toHeight = toCollection[0].height; - const toVerticalGap = 0.3 * toHeight; - const toHorizontalGap = 0.3 * toWidth; - - const gap = 2 * (fromWidth + toWidth); - const totalWidth = gap + fromWidth + toWidth; - - const fromTotalHeight = - fromCollection.length * (fromHeight + fromVerticalGap) + fromVerticalGap; - - const toTotalHeight = - toCollection.length * (toHeight + toVerticalGap) + toVerticalGap; - - canvas.height = - fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; - - canvas.width = - totalWidth > window.innerWidth ? totalWidth : window.innerWidth; - - const width = canvas.width; - - const fromX = width / 2 - fromWidth - fromHorizontalGap; - const toX = width / 2 + toHorizontalGap; - - let toCount = 0; - - fromCollection.forEach((fromObject) => { - const objectRelations = fromObject.oneToManyRelations[relationName]; - const length = objectRelations.length; - - objectRelations.forEach((relation) => { - const toObject = relation.to; - - toObject.x = toX; - toObject.y = toVerticalGap + toCount * (toHeight + toVerticalGap); - toCount++; - }); - - fromObject.x = fromX; - const firstY = objectRelations[0].to.y; - const lastY = objectRelations[length - 1].to.y; - - fromObject.y = parseInt((firstY + lastY) / 2); - }); -} diff --git a/js/views/pre-filter.js b/js/views/pre-filter.js index af2a4fd1..8334ee6b 100644 --- a/js/views/pre-filter.js +++ b/js/views/pre-filter.js @@ -37,24 +37,3 @@ export function preFilterTree( currentObjects.datatypes[collectionName].oneToMany[relationName]; }); } - -export function preFilterOneToMany( - currentObjects, - viewObjects, - fromCollectionName, - toCollectionName, - relationName -) { - emptyCopyObject(currentObjects, viewObjects); - - const relations = - currentObjects.datatypes[fromCollectionName].oneToMany[relationName]; - - viewObjects.datatypes[fromCollectionName].oneToMany[relationName] = relations; - - const fromCollection = relations.map((relation) => relation.from); - const toCollection = relations.map((relation) => relation.to); - - viewObjects.datatypes[fromCollectionName].collection = fromCollection; - viewObjects.datatypes[toCollectionName].collection = toCollection; -} diff --git a/js/views/recocluster.js b/js/views/recocluster.js deleted file mode 100644 index 72b92e17..00000000 --- a/js/views/recocluster.js +++ /dev/null @@ -1,21 +0,0 @@ -import { preFilterOneToMany } from "./pre-filter.js"; -import { oneToManyView } from "./oneToMany.js"; - -export function recoClusterView(viewCurrentObjects) { - oneToManyView( - viewCurrentObjects, - "edm4hep::ReconstructedParticle", - "edm4hep::Cluster", - "clusters" - ); -} - -export function preFilterRecoCluster(currentObjects, viewObjects) { - preFilterOneToMany( - currentObjects, - viewObjects, - "edm4hep::ReconstructedParticle", - "edm4hep::Cluster", - "clusters" - ); -} diff --git a/js/views/recoclustertrack.js b/js/views/recoclustertrack.js new file mode 100644 index 00000000..1ecee9fd --- /dev/null +++ b/js/views/recoclustertrack.js @@ -0,0 +1,154 @@ +import { canvas } from "../main.js"; +import { emptyCopyObject } from "../lib/copy.js"; + +export function recoClusterTrack(viewObjects) { + const recoParticles = + viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection; + + if (recoParticles.length === 0) { + alert("No reconstructed particles found!"); + return; + } + + const findFirstObject = (relationName) => { + const object = recoParticles.find((particle) => { + const relation = particle.oneToManyRelations[relationName]; + if (relation.length > 0) { + return relation[0].to; + } + }); + return object; + }; + + const firstRecoParticle = recoParticles[0]; + const recoHeight = firstRecoParticle.height; + const recoVerticalGap = parseInt(recoHeight * 0.3); + const recoVerticalSpace = recoHeight + recoVerticalGap; + const recoHalfHeight = parseInt(recoHeight / 2); + const recoWidth = firstRecoParticle.width; + const recoHorizontalGap = recoWidth * 0.3; + + const firstCluster = findFirstObject("clusters"); + const clusterHeight = firstCluster.height; + const clusterVerticalGap = clusterHeight * 0.3; + const clusterWidth = firstCluster.width; + const firstTrack = findFirstObject("tracks"); + const trackHeight = firstTrack.height; + const trackVerticalGap = trackHeight * 0.3; + const trackWidth = firstTrack.width; + + const widestObject = Math.max(clusterWidth, trackWidth); + const widestGap = widestObject * 0.3; + + const totalHorizontalGap = + 2 * recoHorizontalGap + recoWidth + widestObject + 2 * widestGap; + + const width = + totalHorizontalGap > window.innerWidth + ? totalHorizontalGap + : window.innerWidth; + + canvas.width = width; + + const recoX = width / 2 - recoWidth; + + const otherX = width / 2 + widestGap; + + let totalHeight = 0; + + recoParticles.forEach((particle) => { + const clusterRelations = particle.oneToManyRelations["clusters"]; + const trackRelations = particle.oneToManyRelations["tracks"]; + + const relationsHeight = parseInt( + clusterRelations.length * (clusterHeight + clusterVerticalGap) + + trackRelations.length * (trackHeight + trackVerticalGap) + ); + + const height = + recoVerticalSpace > relationsHeight ? recoVerticalSpace : relationsHeight; + + const recoY = totalHeight + height / 2 - recoHalfHeight; + particle.y = recoY; + particle.x = recoX; + + const initialGap = (height - relationsHeight) / 2; + + let accumulatedRelationsHeight = initialGap + totalHeight; + + clusterRelations.forEach((clusterRelation) => { + const cluster = clusterRelation.to; + cluster.x = otherX; + + const y = clusterVerticalGap / 2 + accumulatedRelationsHeight; + cluster.y = y; + accumulatedRelationsHeight += clusterHeight + clusterVerticalGap / 2; + }); + + trackRelations.forEach((trackRelation) => { + const track = trackRelation.to; + track.x = otherX; + + const y = trackVerticalGap / 2 + accumulatedRelationsHeight; + track.y = y; + accumulatedRelationsHeight += trackHeight + trackVerticalGap / 2; + }); + + totalHeight += height; + }); + + canvas.height = totalHeight; +} + +export function preFilterRecoClusterTrack(currentObjects, viewObjects) { + emptyCopyObject(currentObjects, viewObjects); + + const fromDatatype = + currentObjects.datatypes["edm4hep::ReconstructedParticle"]; + + const fromCollection = fromDatatype.collection; + + const recoParticles = []; + const clusters = []; + const tracks = []; + + fromCollection.forEach((particle) => { + const clusterRelations = particle.oneToManyRelations["clusters"]; + const trackRelations = particle.oneToManyRelations["tracks"]; + + const total = clusterRelations.length + trackRelations.length; + + if (total === 0) { + return; + } + + clusterRelations.forEach((clusterRelation) => { + const cluster = clusterRelation.to; + clusters.push(cluster); + }); + + trackRelations.forEach((trackRelation) => { + const track = trackRelation.to; + tracks.push(track); + }); + + recoParticles.push(particle); + }); + + viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection = + recoParticles; + viewObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany[ + "clusters" + ] = + currentObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany[ + "clusters" + ]; + viewObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany["tracks"] = + currentObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany[ + "tracks" + ]; + + viewObjects.datatypes["edm4hep::Cluster"].collection = clusters; + + viewObjects.datatypes["edm4hep::Track"].collection = tracks; +} diff --git a/js/views/recotrack.js b/js/views/recotrack.js deleted file mode 100644 index 39b1793a..00000000 --- a/js/views/recotrack.js +++ /dev/null @@ -1,21 +0,0 @@ -import { preFilterOneToMany } from "./pre-filter.js"; -import { oneToManyView } from "./oneToMany.js"; - -export function recoTrackView(viewCurrentObjects) { - oneToManyView( - viewCurrentObjects, - "edm4hep::ReconstructedParticle", - "edm4hep::Track", - "tracks" - ); -} - -export function preFilterRecoTrack(currentObjects, viewObjects) { - preFilterOneToMany( - currentObjects, - viewObjects, - "edm4hep::ReconstructedParticle", - "edm4hep::Track", - "tracks" - ); -} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index df43922b..80384920 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -10,8 +10,10 @@ import { preFilterMCCluster, mcClusterAssociation, } from "./mcclusterassociation.js"; -import { recoTrackView, preFilterRecoTrack } from "./recotrack.js"; -import { recoClusterView, preFilterRecoCluster } from "./recocluster.js"; +import { + recoClusterTrack, + preFilterRecoClusterTrack, +} from "./recoclustertrack.js"; export const views = { "Monte Carlo Particle Tree": { @@ -38,17 +40,11 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterClusterTree, }, - "Reconstructed Particle-Cluster": { - filters: () => {}, - viewFunction: recoClusterView, - scrollFunction: scrollTopCenter, - preFilterFunction: preFilterRecoCluster, - }, - "Reconstructed Particle-Track": { + "Reco Particle-Cluster-Track": { filters: () => {}, - viewFunction: recoTrackView, + viewFunction: recoClusterTrack, scrollFunction: scrollTopCenter, - preFilterFunction: preFilterRecoTrack, + preFilterFunction: preFilterRecoClusterTrack, }, "Monte Carlo-Reconstructed Particle": { filters: () => {}, From 9b592d223e6b664ac37048d6b9e076573e4f3470 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 6 Jul 2024 19:37:36 -0500 Subject: [PATCH 19/60] fix associations views when different heights --- js/views/association-view.js | 38 ++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/js/views/association-view.js b/js/views/association-view.js index 92bcd3ff..00eeb28c 100644 --- a/js/views/association-view.js +++ b/js/views/association-view.js @@ -19,33 +19,33 @@ export function buildAssociationView(viewObjects, associationName) { const gap = 2 * (fromWidth + toWidth); const totalWidth = gap + fromWidth + toWidth; - canvas.width = - totalWidth > window.innerWidth ? totalWidth : window.innerWidth; - - const width = canvas.width; + const width = totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + canvas.width = width; const fromHeight = fromCollection[0].height; const toHeight = toCollection[0].height; - const fromVerticalGap = 0.3 * fromHeight; - const toVerticalGap = 0.3 * toHeight; - const fromTotalHeight = - fromCollection.length * (fromHeight + fromVerticalGap) + fromVerticalGap; - const toTotalHeight = - toCollection.length * (toHeight + toVerticalGap) + toVerticalGap; + const height = Math.max(fromHeight, toHeight); + const verticalGap = 0.3 * height; + + const totalHeight = + fromCollection.length * (height + verticalGap) + verticalGap; + + canvas.height = totalHeight; - canvas.height = - fromTotalHeight > toTotalHeight ? fromTotalHeight : toTotalHeight; + let accHeight = 0; const fromX = width / 2 - fromWidth - fromHorizontalGap; - for (const [index, from] of fromCollection.entries()) { - from.y = fromVerticalGap + index * (fromHeight + fromVerticalGap); - from.x = fromX; - } const toX = width / 2 + toHorizontalGap; - for (const [index, to] of toCollection.entries()) { - to.y = toVerticalGap + index * (toHeight + toVerticalGap); - to.x = toX; + + for (let i = 0; i < fromCollection.length; i++) { + fromCollection[i].x = fromX; + toCollection[i].x = toX; + + const space = height + verticalGap; + fromCollection[i].y = accHeight + space / 2 - fromHeight / 2; + toCollection[i].y = accHeight + space / 2 - toHeight / 2; + accHeight += height + verticalGap; } } From 4806d73d197fb10436cb7451f13a45682209dd7e Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 6 Jul 2024 19:46:46 -0500 Subject: [PATCH 20/60] show momentum, energy and charge for reco particle --- js/types/objects.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/js/types/objects.js b/js/types/objects.js index 28d0ed52..1bf8d773 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -218,11 +218,20 @@ class ReconstructedParticle extends EDMObject { const topY = this.y + 20; const lines = []; + lines.push("ID: " + this.index); + + const x = parseInt(this.momentum.x * 100) / 100; + const y = parseInt(this.momentum.y * 100) / 100; + const z = parseInt(this.momentum.z * 100) / 100; + lines.push(`p = (x=${x},`); + lines.push(`y=${y},`); + lines.push(`z=${z}) GeV`); + const energy = parseInt(this.energy * 100) / 100; lines.push("e = " + energy + " GeV"); + lines.push(parseCharge(this.charge)); - lines.push("pdg = " + this.PDG); drawTextLines(ctx, lines, boxCenterX, topY, 23); } @@ -261,9 +270,9 @@ class Cluster extends EDMObject { const x = parseInt(this.position.x * 100) / 100; const y = parseInt(this.position.y * 100) / 100; const z = parseInt(this.position.z * 100) / 100; - lines.push(`pos = (${x},`); - lines.push(`${y},`); - lines.push(`${z}) mm`); + lines.push(`pos = (x=${x},`); + lines.push(`y=${y},`); + lines.push(`z=${z}) mm`); drawTextLines(ctx, lines, boxCenterX, topY, 23); } From de191b1e46bf171227d5b75cff936078e1b04a51 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 6 Jul 2024 20:20:08 -0500 Subject: [PATCH 21/60] show filters only on mc view --- js/filter/nofilter.js | 7 +++++++ js/menu/filter/reconnect.js | 8 ++++---- js/views/views-dictionary.js | 15 ++++++++------- 3 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 js/filter/nofilter.js diff --git a/js/filter/nofilter.js b/js/filter/nofilter.js new file mode 100644 index 00000000..0e41a3fc --- /dev/null +++ b/js/filter/nofilter.js @@ -0,0 +1,7 @@ +export function setupNoFilter() { + const manipulationTools = + document.getElementsByClassName("manipulation-tool"); + for (const tool of manipulationTools) { + tool.style.display = "none"; + } +} diff --git a/js/menu/filter/reconnect.js b/js/menu/filter/reconnect.js index 4ff272b6..28ccac22 100644 --- a/js/menu/filter/reconnect.js +++ b/js/menu/filter/reconnect.js @@ -6,11 +6,11 @@ export function reconnect(criteriaFunction, loadedObjects) { emptyCopyObject(loadedObjects, filteredObjects); - for (const [key, value] of Object.entries(loadedObjects.datatypes)) { - const filterFunction = objectTypes[key].filter; + const filterFunction = objectTypes["edm4hep::MCParticle"].filter; - filterFunction(value, filteredObjects.datatypes, criteriaFunction); - } + const mcParticles = loadedObjects.datatypes["edm4hep::MCParticle"]; + + filterFunction(mcParticles, filteredObjects.datatypes, criteriaFunction); return filteredObjects; } diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 80384920..716983d1 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -14,6 +14,7 @@ import { recoClusterTrack, preFilterRecoClusterTrack, } from "./recoclustertrack.js"; +import { setupNoFilter } from "../filter/nofilter.js"; export const views = { "Monte Carlo Particle Tree": { @@ -23,43 +24,43 @@ export const views = { preFilterFunction: preFilterMCTree, }, "Reconstructed Particle Tree": { - filters: () => {}, + filters: setupNoFilter, viewFunction: recoParticleTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterRecoTree, }, "Track Tree": { - filters: () => {}, + filters: setupNoFilter, viewFunction: trackTree, scrollFunction: scrollTopCenter, preFilterFunction: preFilterTrackTree, }, "Cluster Tree": { - filters: () => {}, + filters: setupNoFilter, viewFunction: clusterTree, scrollFunction: scrollTopCenter, preFilterFunction: preFilterClusterTree, }, "Reco Particle-Cluster-Track": { - filters: () => {}, + filters: setupNoFilter, viewFunction: recoClusterTrack, scrollFunction: scrollTopCenter, preFilterFunction: preFilterRecoClusterTrack, }, "Monte Carlo-Reconstructed Particle": { - filters: () => {}, + filters: setupNoFilter, viewFunction: mcRecoAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCReco, }, "Monte Carlo Particle-Track": { - filters: () => {}, + filters: setupNoFilter, viewFunction: mcTrackAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCTrack, }, "Monte Carlo Particle-Cluster": { - filters: () => {}, + filters: setupNoFilter, viewFunction: mcClusterAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCCluster, From 8dc35d1e55c331ecc70320dc4e0bfe63d9dff555 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 9 Jul 2024 20:24:10 -0500 Subject: [PATCH 22/60] initial views for particleid and vertex --- js/main.js | 2 ++ js/types/links.js | 12 +++++++- js/types/load.js | 17 +++++++---- js/types/objects.js | 55 +++++++++++++++++++++++++++++++++++- js/views/list.js | 32 +++++++++++++++++++++ js/views/onewayview.js | 51 +++++++++++++++++++++++++++++++++ js/views/particleidlist.js | 13 +++++++++ js/views/pre-filter.js | 27 ++++++++++++++++++ js/views/recoparticleid.js | 16 +++++++++++ js/views/vertexlist.js | 13 +++++++++ js/views/views-dictionary.js | 25 ++++++++++++++-- output/datatypes.js | 7 ++--- 12 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 js/views/list.js create mode 100644 js/views/onewayview.js create mode 100644 js/views/particleidlist.js create mode 100644 js/views/recoparticleid.js create mode 100644 js/views/vertexlist.js diff --git a/js/main.js b/js/main.js index 192fcd09..e9ab8b74 100644 --- a/js/main.js +++ b/js/main.js @@ -20,6 +20,8 @@ const selectedObjectTypes = { "edm4hep::MCRecoClusterParticleAssociation", "edm4hep::Cluster", "edm4hep::Track", + "edm4hep::Vertex", + "edm4hep::ParticleID", ], }; diff --git a/js/types/links.js b/js/types/links.js index fa19e997..5a7c7391 100644 --- a/js/types/links.js +++ b/js/types/links.js @@ -9,6 +9,7 @@ const colors = { "particles": "#AA00AA", "mcclusters": "#D8F1A0", "mctracks": "#fe5e41", + "vertex": "#593746", }; export class Link { @@ -106,6 +107,13 @@ class Tracks extends Link { } } +class Vertex extends Link { + constructor(from, to) { + super(from, to); + this.color = colors["vertex"]; + } +} + class MCRecoTrackParticleAssociation extends Link { constructor(from, to, weight) { super(from, to); @@ -139,5 +147,7 @@ export const linkTypes = { "clusters": Clusters, "tracks": Tracks, "particles": Particles, - "startVertex": Link, + "particle": Particles, + "startVertex": Vertex, + "associatedParticle": Vertex, }; diff --git a/js/types/load.js b/js/types/load.js index d2b12bd7..b9ee63d7 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -102,9 +102,12 @@ export function loadObjects(jsonData, event, objectsToLoad) { // 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 oneToOneRelationData = element.collection + .map((object) => object[name]) + .filter((object) => object !== undefined); + + if (oneToOneRelationData.length === 0) continue; + const toCollectionID = oneToOneRelationData.find( (relation) => relation.collectionID !== undefined @@ -131,9 +134,11 @@ export function loadObjects(jsonData, event, objectsToLoad) { // 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 oneToManyRelationData = element.collection + .map((object) => object[name]) + .filter((object) => object !== undefined); + + if (oneToManyRelationData.length === 0) continue; const toCollectionID = oneToManyRelationData.find( diff --git a/js/types/objects.js b/js/types/objects.js index 1bf8d773..f3cae588 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -307,7 +307,7 @@ class Track extends EDMObject { lines.push("type: " + this.type); const chi2 = parseInt(this.chi2 * 100) / 100; const ndf = parseInt(this.ndf * 100) / 100; - const chiNdf = `${chi2} / ${ndf}`; + const chiNdf = `${chi2}/${ndf}`; lines.push("chi2/ndf = " + chiNdf); lines.push("dEdx = " + this.dEdx); @@ -323,13 +323,66 @@ class Track extends EDMObject { class ParticleID extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 120; } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#c9edf7", + 25 + ); + } + + static setup(particleIDCollection) {} } class Vertex extends EDMObject { constructor() { super(); + this.width = 140; + this.height = 140; + } + + draw(ctx) { + const boxCenterX = this.x + this.width / 2; + + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + "#f5d3ef", + 25 + ); + + const topY = this.y + 20; + + const lines = []; + lines.push("ID: " + this.index); + const x = parseInt(this.position.x * 100) / 100; + const y = parseInt(this.position.y * 100) / 100; + const z = parseInt(this.position.z * 100) / 100; + lines.push(`pos = (x=${x},`); + lines.push(`y=${y},`); + lines.push(`z=${z}) mm`); + const chi2 = parseInt(this.chi2 * 100) / 100; + const ndf = parseInt(this.ndf * 100) / 100; + const chiNdf = `${chi2}/${ndf}`; + lines.push("chi2/ndf = " + chiNdf); + + drawTextLines(ctx, lines, boxCenterX, topY, 23); } + + static setup(vertexCollection) {} } export const objectTypes = { diff --git a/js/views/list.js b/js/views/list.js new file mode 100644 index 00000000..99aa7ce4 --- /dev/null +++ b/js/views/list.js @@ -0,0 +1,32 @@ +import { canvas } from "../main.js"; + +export function listView(collection) { + if (collection.length === 0) { + alert("No objects found!"); + return; + } + const width = window.innerWidth; + canvas.width = width; + + const gap = 1; + const objWidth = collection[0].width; + const objHorizontalGap = gap * objWidth; + const objHeight = collection[0].height; + const objVerticalGap = gap * objHeight; + + const cols = Math.ceil(width / (objWidth + objHorizontalGap)); + const rows = Math.ceil(collection.length / cols); + + const height = rows * (objHeight + objVerticalGap / 2) + objVerticalGap / 2; + canvas.height = height > window.innerHeight ? height : window.innerHeight; + + for (let i = 0; i < collection.length; i++) { + const x = (i % cols) * objWidth + (((i % cols) + 1) * objHorizontalGap) / 2; + const y = + Math.floor(i / cols) * objHeight + + ((Math.floor(i / cols) + 1) * objVerticalGap) / 2; + + collection[i].x = x; + collection[i].y = y; + } +} diff --git a/js/views/onewayview.js b/js/views/onewayview.js new file mode 100644 index 00000000..1a6f8683 --- /dev/null +++ b/js/views/onewayview.js @@ -0,0 +1,51 @@ +import { canvas } from "../main.js"; + +export function oneWayView(viewObjects, fromCollectionName, relationName) { + const relations = + viewObjects.datatypes[fromCollectionName].oneToOne[relationName]; + + const fromCollection = relations.map((relation) => relation.from); + const toCollection = relations.map((relation) => relation.to); + + if (fromCollection.length === 0 || toCollection.length === 0) { + alert("No association found!"); + return; + } + + const fromWidth = fromCollection[0].width; + const toWidth = toCollection[0].width; + const fromHorizontalGap = 0.3 * fromWidth; + const toHorizontalGap = 0.3 * toWidth; + const gap = 2 * (fromWidth + toWidth); + const totalWidth = gap + fromWidth + toWidth; + + const width = totalWidth > window.innerWidth ? totalWidth : window.innerWidth; + canvas.width = width; + + const fromHeight = fromCollection[0].height; + const toHeight = toCollection[0].height; + + const height = Math.max(fromHeight, toHeight); + const verticalGap = 0.3 * height; + + const totalHeight = + fromCollection.length * (height + verticalGap) + verticalGap; + + canvas.height = totalHeight; + + let accHeight = 0; + + const fromX = width / 2 - fromWidth - fromHorizontalGap; + + const toX = width / 2 + toHorizontalGap; + + for (let i = 0; i < fromCollection.length; i++) { + fromCollection[i].x = fromX; + toCollection[i].x = toX; + + const space = height + verticalGap; + fromCollection[i].y = accHeight + space / 2 - fromHeight / 2; + toCollection[i].y = accHeight + space / 2 - toHeight / 2; + accHeight += height + verticalGap; + } +} diff --git a/js/views/particleidlist.js b/js/views/particleidlist.js new file mode 100644 index 00000000..c3c13cb6 --- /dev/null +++ b/js/views/particleidlist.js @@ -0,0 +1,13 @@ +import { listView } from "./list.js"; +import { preFilterList } from "./pre-filter.js"; + +export function particleIDList(viewCurrentObjects) { + const vertexCollection = + viewCurrentObjects.datatypes["edm4hep::ParticleID"].collection ?? []; + + listView(vertexCollection); +} + +export function preFilterParticleIDList(currentObjects, viewObjects) { + preFilterList(currentObjects, viewObjects, "edm4hep::ParticleID"); +} diff --git a/js/views/pre-filter.js b/js/views/pre-filter.js index 8334ee6b..bad016ed 100644 --- a/js/views/pre-filter.js +++ b/js/views/pre-filter.js @@ -37,3 +37,30 @@ export function preFilterTree( currentObjects.datatypes[collectionName].oneToMany[relationName]; }); } + +export function preFilterList(currentObjects, viewObjects, collectionName) { + emptyCopyObject(currentObjects, viewObjects); + + viewObjects.datatypes[collectionName].collection = + currentObjects.datatypes[collectionName].collection; +} + +export function preFilterOneWay( + currentObjects, + viewObjects, + relationName, + fromCollectionName, + toCollectionName +) { + emptyCopyObject(currentObjects, viewObjects); + + const relations = + currentObjects.datatypes[fromCollectionName].oneToOne[relationName]; + + const fromCollection = relations.map((relation) => relation.from); + const toCollection = relations.map((relation) => relation.to); + + viewObjects.datatypes[fromCollectionName].oneToOne[relationName] = relations; + viewObjects.datatypes[fromCollectionName].collection = fromCollection; + viewObjects.datatypes[toCollectionName].collection = toCollection; +} diff --git a/js/views/recoparticleid.js b/js/views/recoparticleid.js new file mode 100644 index 00000000..e145b9de --- /dev/null +++ b/js/views/recoparticleid.js @@ -0,0 +1,16 @@ +import { preFilterOneWay } from "./pre-filter.js"; +import { oneWayView } from "./onewayview.js"; + +export function recoParticleID(viewObjects) { + oneWayView(viewObjects, "edm4hep::ParticleID", "particle"); +} + +export function preFilterRecoParticleID(currentObjects, viewObjects) { + preFilterOneWay( + currentObjects, + viewObjects, + "particle", + "edm4hep::ParticleID", + "edm4hep::ReconstructedParticle" + ); +} diff --git a/js/views/vertexlist.js b/js/views/vertexlist.js new file mode 100644 index 00000000..59962c5f --- /dev/null +++ b/js/views/vertexlist.js @@ -0,0 +1,13 @@ +import { listView } from "./list.js"; +import { preFilterList } from "./pre-filter.js"; + +export function vertexList(viewCurrentObjects) { + const vertexCollection = + viewCurrentObjects.datatypes["edm4hep::Vertex"].collection ?? []; + + listView(vertexCollection); +} + +export function preFilterVertexList(currentObjects, viewObjects) { + preFilterList(currentObjects, viewObjects, "edm4hep::Vertex"); +} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 716983d1..fa3cde07 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -15,6 +15,9 @@ import { preFilterRecoClusterTrack, } from "./recoclustertrack.js"; import { setupNoFilter } from "../filter/nofilter.js"; +import { vertexList, preFilterVertexList } from "./vertexlist.js"; +import { particleIDList, preFilterParticleIDList } from "./particleidlist.js"; +import { recoParticleID, preFilterRecoParticleID } from "./recoparticleid.js"; export const views = { "Monte Carlo Particle Tree": { @@ -32,13 +35,13 @@ export const views = { "Track Tree": { filters: setupNoFilter, viewFunction: trackTree, - scrollFunction: scrollTopCenter, + scrollFunction: scrollTopLeft, preFilterFunction: preFilterTrackTree, }, "Cluster Tree": { filters: setupNoFilter, viewFunction: clusterTree, - scrollFunction: scrollTopCenter, + scrollFunction: scrollTopLeft, preFilterFunction: preFilterClusterTree, }, "Reco Particle-Cluster-Track": { @@ -65,4 +68,22 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCCluster, }, + "ParticleID List": { + filters: setupNoFilter, + viewFunction: particleIDList, + scrollFunction: scrollTopLeft, + preFilterFunction: preFilterParticleIDList, + }, + "Vertex List": { + filters: setupNoFilter, + viewFunction: vertexList, + scrollFunction: scrollTopLeft, + preFilterFunction: preFilterVertexList, + }, + "ParticleID-Reconstructed Particle": { + filters: setupNoFilter, + viewFunction: recoParticleID, + scrollFunction: scrollTopCenter, + preFilterFunction: preFilterRecoParticleID, + }, }; diff --git a/output/datatypes.js b/output/datatypes.js index 31826a96..92cb2b88 100644 --- a/output/datatypes.js +++ b/output/datatypes.js @@ -135,9 +135,6 @@ export const datatypes = { }, { "name": "dEdxError" - }, - { - "name": "radiusOfInnermostHit" } ], "oneToManyRelations": [ @@ -154,13 +151,13 @@ export const datatypes = { "edm4hep::Vertex": { "members": [ { - "name": "primary" + "name": "type" }, { "name": "chi2" }, { - "name": "probability" + "name": "ndf" }, { "name": "position" From bc5e1a28aaa73ffe2e7ceab3034ee128171cba13 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Thu, 11 Jul 2024 16:27:14 -0500 Subject: [PATCH 23/60] add startvertex to same view as recoparticle-cluster-track --- js/views/recoclustertrack.js | 50 ++++++++++++++++++++++++++++++++---- js/views/views-dictionary.js | 10 ++++---- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/js/views/recoclustertrack.js b/js/views/recoclustertrack.js index 1ecee9fd..874f9630 100644 --- a/js/views/recoclustertrack.js +++ b/js/views/recoclustertrack.js @@ -1,7 +1,7 @@ import { canvas } from "../main.js"; import { emptyCopyObject } from "../lib/copy.js"; -export function recoClusterTrack(viewObjects) { +export function recoClusterTrackVertex(viewObjects) { const recoParticles = viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection; @@ -37,7 +37,23 @@ export function recoClusterTrack(viewObjects) { const trackVerticalGap = trackHeight * 0.3; const trackWidth = firstTrack.width; - const widestObject = Math.max(clusterWidth, trackWidth); + const firstVertex = recoParticles.find((particle) => { + const vertexRelation = particle.oneToOneRelations["startVertex"]; + if (vertexRelation !== undefined) { + return vertexRelation.to; + } + }); + + let vertexHeight = 0; + let vertexVerticalGap = 0; + let vertexWidth = 0; + if (firstVertex !== undefined) { + vertexHeight = firstVertex.height; + vertexVerticalGap = vertexHeight * 0.3; + vertexWidth = firstVertex.width; + } + + const widestObject = Math.max(clusterWidth, trackWidth, vertexWidth); const widestGap = widestObject * 0.3; const totalHorizontalGap = @@ -59,10 +75,12 @@ export function recoClusterTrack(viewObjects) { recoParticles.forEach((particle) => { const clusterRelations = particle.oneToManyRelations["clusters"]; const trackRelations = particle.oneToManyRelations["tracks"]; + const vertexRelation = particle.oneToOneRelations["startVertex"]; const relationsHeight = parseInt( clusterRelations.length * (clusterHeight + clusterVerticalGap) + - trackRelations.length * (trackHeight + trackVerticalGap) + trackRelations.length * (trackHeight + trackVerticalGap) + + (vertexRelation !== undefined ? vertexHeight + vertexVerticalGap : 0) ); const height = @@ -94,13 +112,22 @@ export function recoClusterTrack(viewObjects) { accumulatedRelationsHeight += trackHeight + trackVerticalGap / 2; }); + if (vertexRelation !== undefined) { + const vertex = vertexRelation.to; + vertex.x = otherX; + + const y = vertexVerticalGap / 2 + accumulatedRelationsHeight; + vertex.y = y; + accumulatedRelationsHeight += vertexHeight + vertexVerticalGap / 2; + } + totalHeight += height; }); canvas.height = totalHeight; } -export function preFilterRecoClusterTrack(currentObjects, viewObjects) { +export function preFilterRecoClusterTrackVertex(currentObjects, viewObjects) { emptyCopyObject(currentObjects, viewObjects); const fromDatatype = @@ -111,10 +138,12 @@ export function preFilterRecoClusterTrack(currentObjects, viewObjects) { const recoParticles = []; const clusters = []; const tracks = []; + const vertexCollection = []; fromCollection.forEach((particle) => { const clusterRelations = particle.oneToManyRelations["clusters"]; const trackRelations = particle.oneToManyRelations["tracks"]; + const vertexRelation = particle.oneToOneRelations["startVertex"]; const total = clusterRelations.length + trackRelations.length; @@ -132,6 +161,11 @@ export function preFilterRecoClusterTrack(currentObjects, viewObjects) { tracks.push(track); }); + if (vertexRelation !== undefined) { + const vertex = vertexRelation.to; + vertexCollection.push(vertex); + } + recoParticles.push(particle); }); @@ -147,8 +181,14 @@ export function preFilterRecoClusterTrack(currentObjects, viewObjects) { currentObjects.datatypes["edm4hep::ReconstructedParticle"].oneToMany[ "tracks" ]; + viewObjects.datatypes["edm4hep::ReconstructedParticle"].oneToOne[ + "startVertex" + ] = + currentObjects.datatypes["edm4hep::ReconstructedParticle"].oneToOne[ + "startVertex" + ]; viewObjects.datatypes["edm4hep::Cluster"].collection = clusters; - viewObjects.datatypes["edm4hep::Track"].collection = tracks; + viewObjects.datatypes["edm4hep::Vertex"].collection = vertexCollection; } diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index fa3cde07..03112d6d 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -11,8 +11,8 @@ import { mcClusterAssociation, } from "./mcclusterassociation.js"; import { - recoClusterTrack, - preFilterRecoClusterTrack, + recoClusterTrackVertex, + preFilterRecoClusterTrackVertex, } from "./recoclustertrack.js"; import { setupNoFilter } from "../filter/nofilter.js"; import { vertexList, preFilterVertexList } from "./vertexlist.js"; @@ -44,11 +44,11 @@ export const views = { scrollFunction: scrollTopLeft, preFilterFunction: preFilterClusterTree, }, - "Reco Particle-Cluster-Track": { + "RecoParticle-Cluster-Track-Vertex": { filters: setupNoFilter, - viewFunction: recoClusterTrack, + viewFunction: recoClusterTrackVertex, scrollFunction: scrollTopCenter, - preFilterFunction: preFilterRecoClusterTrack, + preFilterFunction: preFilterRecoClusterTrackVertex, }, "Monte Carlo-Reconstructed Particle": { filters: setupNoFilter, From 6cb7a155090c5b41d9a9efd7fd93986143c56242 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Thu, 11 Jul 2024 16:31:13 -0500 Subject: [PATCH 24/60] show basic properties from ParticleID --- js/types/objects.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/js/types/objects.js b/js/types/objects.js index f3cae588..5d526164 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -339,6 +339,17 @@ class ParticleID extends EDMObject { "#c9edf7", 25 ); + + const topY = this.y + 20; + + const lines = []; + lines.push("ID: " + this.index); + lines.push("type: " + this.type); + lines.push("PDG: " + this.PDG); + lines.push("algorithm: " + this.algorithmType); + lines.push("likelihood: " + this.likelihood); + + drawTextLines(ctx, lines, boxCenterX, topY, 23); } static setup(particleIDCollection) {} From 67255ec70a21c87b5740b1ed6dce7e4427ee5355 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Thu, 11 Jul 2024 18:32:57 -0500 Subject: [PATCH 25/60] todo: fix when changing original x,y from object --- js/views/association-view.js | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/js/views/association-view.js b/js/views/association-view.js index 00eeb28c..5c931c04 100644 --- a/js/views/association-view.js +++ b/js/views/association-view.js @@ -2,18 +2,16 @@ import { canvas } from "../main.js"; // List 1:1 association in a vertical list export function buildAssociationView(viewObjects, associationName) { - const association = viewObjects.associations[associationName]; + const associations = viewObjects.associations[associationName]; + const length = associations.length; - const fromCollection = association.map((association) => association.from); - const toCollection = association.map((association) => association.to); - - if (fromCollection.length === 0 || toCollection.length === 0) { + if (length === 0) { alert("No association found!"); return; } - const fromWidth = fromCollection[0].width; - const toWidth = toCollection[0].width; + const fromWidth = associations[0].from.width; + const toWidth = associations[0].to.width; const fromHorizontalGap = 0.3 * fromWidth; const toHorizontalGap = 0.3 * toWidth; const gap = 2 * (fromWidth + toWidth); @@ -22,14 +20,13 @@ export function buildAssociationView(viewObjects, associationName) { const width = totalWidth > window.innerWidth ? totalWidth : window.innerWidth; canvas.width = width; - const fromHeight = fromCollection[0].height; - const toHeight = toCollection[0].height; + const fromHeight = associations[0].from.height; + const toHeight = associations[0].to.height; const height = Math.max(fromHeight, toHeight); const verticalGap = 0.3 * height; - const totalHeight = - fromCollection.length * (height + verticalGap) + verticalGap; + const totalHeight = length * (height + verticalGap) + verticalGap; canvas.height = totalHeight; @@ -39,13 +36,15 @@ export function buildAssociationView(viewObjects, associationName) { const toX = width / 2 + toHorizontalGap; - for (let i = 0; i < fromCollection.length; i++) { - fromCollection[i].x = fromX; - toCollection[i].x = toX; + associations.forEach((association) => { + association.from.x = fromX; + association.to.x = toX; const space = height + verticalGap; - fromCollection[i].y = accHeight + space / 2 - fromHeight / 2; - toCollection[i].y = accHeight + space / 2 - toHeight / 2; + const fromY = accHeight + space / 2 - fromHeight / 2; + const toY = accHeight + space / 2 - toHeight / 2; + association.from.y = fromY; + association.to.y = toY; accHeight += height + verticalGap; - } + }); } From 3d93f2973ceb7a0c76c1be3ee0718214562d485c Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Thu, 11 Jul 2024 19:33:10 -0500 Subject: [PATCH 26/60] add switcher between release and main --- css/switch-deploy.css | 23 +++++++++++++++++++++++ index.html | 6 ++++++ js/switch-deploy.js | 22 ++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 css/switch-deploy.css create mode 100644 js/switch-deploy.js diff --git a/css/switch-deploy.css b/css/switch-deploy.css new file mode 100644 index 00000000..5f496930 --- /dev/null +++ b/css/switch-deploy.css @@ -0,0 +1,23 @@ +#switch-deploy { + display: flex; + flex-direction: row; + align-items: center; +} + +#switch-deploy-button { + cursor: pointer; + background-color: #fff; + border: 1px solid #000; + padding: 5px; + border-radius: 5px; + font-family: sans-serif; + font-size: 14px; +} + +#switch-deploy-button:hover { + background-color: #c5c5c5; +} + +#switch-deploy-text { + margin: 0 10px 0 0; +} diff --git a/index.html b/index.html index 3cb6744e..700ccfbf 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,7 @@ + @@ -148,6 +149,10 @@
+
+

Switch to

+ +
@@ -162,6 +167,7 @@ + \ No newline at end of file diff --git a/js/switch-deploy.js b/js/switch-deploy.js new file mode 100644 index 00000000..445ae82b --- /dev/null +++ b/js/switch-deploy.js @@ -0,0 +1,22 @@ +const button = document.getElementById("switch-deploy-button"); + +button.addEventListener("click", () => { + const currentUrl = window.location.href; + + if (currentUrl.includes("/main")) { + window.location.href = currentUrl.replace("/main", "/release"); + } else if (currentUrl.includes("/release")) { + window.location.href = currentUrl.replace("/release", "/main"); + } else { + window.location.href = "https://key4hep.github.io/eede/release/index.html"; + } +}); + +const url = window.location.href; +if (url.includes("/main")) { + button.innerText = "Release"; +} else if (url.includes("/release")) { + button.innerText = "Main"; +} else { + button.innerText = "Release"; +} From fa4fbbd30fd9eebf8ab50186c732d1c7a21bf2b2 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 10:19:02 -0500 Subject: [PATCH 27/60] show switcher on home page instead of modal --- css/switch-deploy.css | 11 ++++++++++- index.html | 9 +++++---- js/main.js | 7 +++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/css/switch-deploy.css b/css/switch-deploy.css index 5f496930..dea8a671 100644 --- a/css/switch-deploy.css +++ b/css/switch-deploy.css @@ -1,7 +1,15 @@ #switch-deploy { + position: fixed; + left: 10px; + bottom: 10px; display: flex; flex-direction: row; align-items: center; + z-index: 1; + background-color: #fff; + padding: 5px; + border-radius: 5px; + border: 1px solid #000; } #switch-deploy-button { @@ -16,8 +24,9 @@ #switch-deploy-button:hover { background-color: #c5c5c5; + cursor: pointer; } #switch-deploy-text { - margin: 0 10px 0 0; + margin: 0 7px 0 0; } diff --git a/index.html b/index.html index 700ccfbf..48d7fdab 100644 --- a/index.html +++ b/index.html @@ -149,10 +149,6 @@
-
-

Switch to

- -
@@ -160,6 +156,11 @@
+
+

Switch to

+ +
+ diff --git a/js/main.js b/js/main.js index 192fcd09..176a5e2f 100644 --- a/js/main.js +++ b/js/main.js @@ -41,6 +41,12 @@ function showViewsMenu() { viewsMenu.style.display = "flex"; } +function hideDeploySwitch() { + const deploySwitch = document.getElementById("switch-deploy"); + + deploySwitch.style.display = "none"; +} + document.getElementById("input-file").addEventListener("change", (event) => { for (const file of event.target.files) { if (!file.name.endsWith("edm4hep.json")) { @@ -133,6 +139,7 @@ document showEventSwitcher(); showViewsMenu(); renderEvent(eventNum); + hideDeploySwitch(); }); export { canvas, ctx, jsonData, selectedObjectTypes }; From d2b072dda5f70f77d7e633241a04fe8f201896d6 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 11:29:28 -0500 Subject: [PATCH 28/60] create helper function to draw object header --- js/lib/graphic-primitives.js | 11 +++++++++++ js/types/objects.js | 2 -- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/js/lib/graphic-primitives.js b/js/lib/graphic-primitives.js index 2e3438f5..64119846 100644 --- a/js/lib/graphic-primitives.js +++ b/js/lib/graphic-primitives.js @@ -107,3 +107,14 @@ export function drawStraightLink(ctx, link) { ctx.stroke(); ctx.restore(); } + +export function drawObjectHeader(ctx, object, y) { + ctx.save(); + ctx.font = "16px sans-serif"; + ctx.fontWeight = "bold"; + const text = object.constructor.name; + const boxCenterX = object.x + object.width / 2; + const x = boxCenterX - ctx.measureText(text).width / 2; + ctx.fillText(text, x, y); + ctx.restore(); +} diff --git a/js/types/objects.js b/js/types/objects.js index 5d526164..57ce68f9 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -44,9 +44,7 @@ class EDMObject { export class MCParticle extends EDMObject { constructor() { super(); - this.row = -1; - this.texImg = null; } From 824c6ddcb3dfd59e71dcdd6df7a41a4d911b5e6c Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 11:59:27 -0500 Subject: [PATCH 29/60] write object name on top --- js/lib/graphic-primitives.js | 21 +++++-- js/types/objects.js | 112 ++++++++++++++--------------------- 2 files changed, 62 insertions(+), 71 deletions(-) diff --git a/js/lib/graphic-primitives.js b/js/lib/graphic-primitives.js index 64119846..f8426ad5 100644 --- a/js/lib/graphic-primitives.js +++ b/js/lib/graphic-primitives.js @@ -108,13 +108,26 @@ export function drawStraightLink(ctx, link) { ctx.restore(); } -export function drawObjectHeader(ctx, object, y) { +export function drawObjectHeader(ctx, object) { ctx.save(); - ctx.font = "16px sans-serif"; - ctx.fontWeight = "bold"; + ctx.font = "bold 16px sans-serif"; const text = object.constructor.name; const boxCenterX = object.x + object.width / 2; + const textWidth = ctx.measureText(text).width; const x = boxCenterX - ctx.measureText(text).width / 2; - ctx.fillText(text, x, y); + const topY = object.y + 20; + + if (textWidth > object.width) { + const lines = text.split(/(?=[A-Z])/); + for (const [i, lineText] of lines.entries()) { + ctx.fillText( + lineText, + boxCenterX - ctx.measureText(lineText).width / 2, + topY + i * 20 + ); + } + } else { + ctx.fillText(text, x, topY); + } ctx.restore(); } diff --git a/js/types/objects.js b/js/types/objects.js index 57ce68f9..483cac06 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -2,11 +2,14 @@ import { drawTex, drawRoundedRect, drawTextLines, + drawObjectHeader, } from "../lib/graphic-primitives.js"; import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; import { parseCharge } from "../lib/parseCharge.js"; +const TOP_MARGIN = 40; + class EDMObject { constructor() { this.x = NaN; @@ -14,13 +17,24 @@ class EDMObject { this.index = NaN; this.collectionId = NaN; this.width = 120; - this.height = 240; + this.height = 260; this.lineColor = "black"; this.lineWidth = 2; this.color = "white"; } - draw(ctx) {} + draw(ctx) { + drawRoundedRect( + ctx, + this.x, + this.y, + this.width, + this.height, + this.color, + this.radius + ); + drawObjectHeader(ctx, this); + } isHere(mouseX, mouseY) { return ( @@ -46,6 +60,8 @@ export class MCParticle extends EDMObject { super(); this.row = -1; this.texImg = null; + this.color = "#dff6ff"; + this.radius = 15; } updateTexImg(text) { @@ -63,15 +79,7 @@ export class MCParticle extends EDMObject { draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect( - ctx, - this.x, - this.y, - this.width, - this.height, - "#dff6ff", - 15 - ); + super.draw(ctx); if (this.texImg.complete) { drawTex( @@ -93,7 +101,7 @@ export class MCParticle extends EDMObject { }; } - const topY = this.y + 20; + const topY = this.y + TOP_MARGIN; const topLines = []; topLines.push("ID: " + this.index); topLines.push("Gen. stat.: " + this.generatorStatus); @@ -198,23 +206,17 @@ class ReconstructedParticle extends EDMObject { constructor() { super(); this.width = 140; - this.height = 180; + this.height = 190; + this.color = "#fbffdf"; + this.radius = 30; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect( - ctx, - this.x, - this.y, - this.width, - this.height, - "#fbffdf", - 30 - ); + super.draw(ctx); - const topY = this.y + 20; + const topY = this.y + 1.5 * TOP_MARGIN; const lines = []; lines.push("ID: " + this.index); @@ -243,23 +245,17 @@ class Cluster extends EDMObject { constructor() { super(); this.width = 140; - this.height = 180; + this.height = 170; + this.color = "#ffe8df"; + this.radius = 20; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect( - ctx, - this.x, - this.y, - this.width, - this.height, - "#ffe8df", - 20 - ); + super.draw(ctx); - const topY = this.y + 20; + const topY = this.y + TOP_MARGIN; const lines = []; lines.push("ID: " + this.index); lines.push("type: " + this.type); @@ -282,23 +278,17 @@ class Track extends EDMObject { constructor() { super(); this.width = 140; - this.height = 180; + this.height = 150; + this.color = "#fff6df"; + this.radius = 25; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect( - ctx, - this.x, - this.y, - this.width, - this.height, - "#fff6df", - 25 - ); + super.draw(ctx); - const topY = this.y + 20; + const topY = this.y + TOP_MARGIN; const lines = []; lines.push("ID: " + this.index); @@ -322,23 +312,17 @@ class ParticleID extends EDMObject { constructor() { super(); this.width = 140; - this.height = 120; + this.height = 140; + this.color = "#c9edf7"; + this.radius = 25; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect( - ctx, - this.x, - this.y, - this.width, - this.height, - "#c9edf7", - 25 - ); + super.draw(ctx); - const topY = this.y + 20; + const topY = this.y + TOP_MARGIN; const lines = []; lines.push("ID: " + this.index); @@ -357,23 +341,17 @@ class Vertex extends EDMObject { constructor() { super(); this.width = 140; - this.height = 140; + this.height = 150; + this.color = "#f5d3ef"; + this.radius = 25; } draw(ctx) { const boxCenterX = this.x + this.width / 2; - drawRoundedRect( - ctx, - this.x, - this.y, - this.width, - this.height, - "#f5d3ef", - 25 - ); + super.draw(ctx); - const topY = this.y + 20; + const topY = this.y + TOP_MARGIN; const lines = []; lines.push("ID: " + this.index); From abff17c159f69d1957cbfa46136dda68b9eb65e5 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 12:06:41 -0500 Subject: [PATCH 30/60] adjust test according to new mcparticle height --- test/objects.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/objects.test.js b/test/objects.test.js index 1c483342..31cdee92 100644 --- a/test/objects.test.js +++ b/test/objects.test.js @@ -108,7 +108,7 @@ describe("Link", () => { secondObject.x = 140; secondObject.y = 250; - expect(link.isVisible(0, 0, 250, 250)).toBe(true); + expect(link.isVisible(0, 0, 300, 300)).toBe(true); }); it("should return false if the link is not visible", () => { From fb30b713780011c36be9d2241eb5ee403df6777c Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 12:13:07 -0500 Subject: [PATCH 31/60] load collection name into object --- js/types/load.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/js/types/load.js b/js/types/load.js index b9ee63d7..201602e4 100644 --- a/js/types/load.js +++ b/js/types/load.js @@ -21,13 +21,19 @@ function loadEmptyRelations(object, relations) { } } -export function loadPlainObject(collection, datatype, collectionId) { +export function loadPlainObject( + collection, + datatype, + collectionId, + collectionName +) { const objects = []; for (const [index, particle] of collection.entries()) { const newObject = new objectTypes[datatype](); newObject.index = index; newObject.collectionId = collectionId; + newObject.collectionName = collectionName; loadMembers(newObject, particle, datatypes[datatype].members); loadEmptyRelations(newObject, datatypes[datatype]); @@ -66,7 +72,7 @@ export function loadObjects(jsonData, event, objectsToLoad) { }); for (const datatype of datatypesToLoad) { - Object.values(eventData).forEach((element) => { + Object.entries(eventData).forEach(([key, element]) => { const collectionName = `${datatype}Collection`; if (element.collType === collectionName) { const collection = element.collection; @@ -74,7 +80,8 @@ export function loadObjects(jsonData, event, objectsToLoad) { const objectCollection = loadPlainObject( collection, datatype, - collectionId + collectionId, + key ); objects.datatypes[datatype].collection.push(...objectCollection); } From ec335b9f632101b6a2def98ad84ea6f8339be59a Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 12:21:18 -0500 Subject: [PATCH 32/60] detect when mouse is over an object --- js/event-number.js | 1 + js/events.js | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/js/event-number.js b/js/event-number.js index 83b773ad..abfac08e 100644 --- a/js/event-number.js +++ b/js/event-number.js @@ -43,6 +43,7 @@ function loadSelectedEvent() { } else { copyObject(eventCollection[currentEvent.event], currentObjects); } + console.log(currentObjects); } export function renderEvent(eventNumber) { diff --git a/js/events.js b/js/events.js index 9c6a0043..d0ff6be9 100644 --- a/js/events.js +++ b/js/events.js @@ -44,14 +44,22 @@ const mouseOut = function (event, dragTools) { }; const mouseMove = function (event, visibleObjects, dragTools) { - if (!dragTools.isDragging) { - return; - } - const boundigClientRect = canvas.getBoundingClientRect(); const mouseX = parseInt(event.clientX - boundigClientRect.x); const mouseY = parseInt(event.clientY - boundigClientRect.y); + for (const { collection } of Object.values(visibleObjects.datatypes)) { + for (const object of collection) { + if (object.isHere(mouseX, mouseY)) { + object.showObjectTip(); + } + } + } + + if (!dragTools.isDragging) { + return; + } + const dx = mouseX - dragTools.prevMouseX; const dy = mouseY - dragTools.prevMouseY; From 1de96bee18853a96ba359136ea25f9f976b36607 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 14:57:11 -0500 Subject: [PATCH 33/60] show collection name from object when hovered --- js/events.js | 22 +++++++++++++++++----- js/lib/graphic-primitives.js | 10 ++++++++++ js/types/objects.js | 5 +++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/js/events.js b/js/events.js index d0ff6be9..51a1f117 100644 --- a/js/events.js +++ b/js/events.js @@ -1,4 +1,4 @@ -import { canvas } from "./main.js"; +import { canvas, ctx } from "./main.js"; import { drawAll, drawVisible } from "./draw.js"; const mouseDown = function (event, visibleObjects, dragTools) { @@ -48,11 +48,23 @@ const mouseMove = function (event, visibleObjects, dragTools) { const mouseX = parseInt(event.clientX - boundigClientRect.x); const mouseY = parseInt(event.clientY - boundigClientRect.y); - for (const { collection } of Object.values(visibleObjects.datatypes)) { - for (const object of collection) { - if (object.isHere(mouseX, mouseY)) { - object.showObjectTip(); + const allObjects = Object.values(visibleObjects.datatypes) + .map((datatype) => datatype.collection) + .flat(); + + for (const object of allObjects) { + if (object.isHere(mouseX, mouseY)) { + if (dragTools.hoveredObject !== object) { + dragTools.hoveredObject = object; + drawVisible(visibleObjects); + setTimeout(() => { + object.showObjectTip(ctx); + }, 200); + setTimeout(() => { + drawVisible(visibleObjects); + }, 2000); } + break; } } diff --git a/js/lib/graphic-primitives.js b/js/lib/graphic-primitives.js index f8426ad5..2defa72b 100644 --- a/js/lib/graphic-primitives.js +++ b/js/lib/graphic-primitives.js @@ -131,3 +131,13 @@ export function drawObjectHeader(ctx, object) { } ctx.restore(); } + +export function drawObjectInfoTip(ctx, object) { + ctx.save(); + const collectionName = "Collection: " + object.collectionName; + const x = object.x + object.width / 2; + const y = object.y - 10; + ctx.font = "bold 12px sans-serif"; + ctx.fillText(collectionName, x, y); + ctx.restore(); +} diff --git a/js/types/objects.js b/js/types/objects.js index 483cac06..9875884b 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -3,6 +3,7 @@ import { drawRoundedRect, drawTextLines, drawObjectHeader, + drawObjectInfoTip, } from "../lib/graphic-primitives.js"; import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; @@ -53,6 +54,10 @@ class EDMObject { y < this.y + this.height ); } + + showObjectTip(ctx) { + drawObjectInfoTip(ctx, this); + } } export class MCParticle extends EDMObject { From 3c9312853176caccb3f0867bb18a1912f8386eeb Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 15:05:12 -0500 Subject: [PATCH 34/60] remove log --- js/event-number.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/event-number.js b/js/event-number.js index abfac08e..83b773ad 100644 --- a/js/event-number.js +++ b/js/event-number.js @@ -43,7 +43,6 @@ function loadSelectedEvent() { } else { copyObject(eventCollection[currentEvent.event], currentObjects); } - console.log(currentObjects); } export function renderEvent(eventNumber) { From db4f5a38d9fae6fc714f4f487f7b2af69ade2785 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 15:13:27 -0500 Subject: [PATCH 35/60] improve hover effect --- js/events.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/js/events.js b/js/events.js index 51a1f117..0c300be8 100644 --- a/js/events.js +++ b/js/events.js @@ -52,22 +52,24 @@ const mouseMove = function (event, visibleObjects, dragTools) { .map((datatype) => datatype.collection) .flat(); + let someHovered = false; for (const object of allObjects) { if (object.isHere(mouseX, mouseY)) { if (dragTools.hoveredObject !== object) { dragTools.hoveredObject = object; drawVisible(visibleObjects); - setTimeout(() => { - object.showObjectTip(ctx); - }, 200); - setTimeout(() => { - drawVisible(visibleObjects); - }, 2000); + object.showObjectTip(ctx); } + someHovered = true; break; } } + if (!someHovered) { + dragTools.hoveredObject = null; + drawVisible(visibleObjects); + } + if (!dragTools.isDragging) { return; } From 3073c29bbfef211084fdbf97d899dd61047b4720 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 15:15:38 -0500 Subject: [PATCH 36/60] move further left text --- js/lib/graphic-primitives.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/lib/graphic-primitives.js b/js/lib/graphic-primitives.js index 2defa72b..5335f754 100644 --- a/js/lib/graphic-primitives.js +++ b/js/lib/graphic-primitives.js @@ -135,7 +135,7 @@ export function drawObjectHeader(ctx, object) { export function drawObjectInfoTip(ctx, object) { ctx.save(); const collectionName = "Collection: " + object.collectionName; - const x = object.x + object.width / 2; + const x = object.x; const y = object.y - 10; ctx.font = "bold 12px sans-serif"; ctx.fillText(collectionName, x, y); From 185d36a004f3077df52c6b3c3da55013a515df79 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 15:36:17 -0500 Subject: [PATCH 37/60] basic information modal for empty view --- css/empty-view.css | 18 ++++++++++++++++++ img/blue-info.svg | 5 +++++ index.html | 6 ++++++ js/lib/messages.js | 8 ++++++++ 4 files changed, 37 insertions(+) create mode 100644 css/empty-view.css create mode 100644 img/blue-info.svg diff --git a/css/empty-view.css b/css/empty-view.css new file mode 100644 index 00000000..da3d2073 --- /dev/null +++ b/css/empty-view.css @@ -0,0 +1,18 @@ +#empty-view { + display: none; + align-items: center; + background-color: #e1e1e1; + padding: 10px; + position: fixed; + z-index: 2; + height: 30px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 5px; + border: 1px solid #000; +} + +#empty-view p { + margin-left: 10px; +} diff --git a/img/blue-info.svg b/img/blue-info.svg new file mode 100644 index 00000000..760cf319 --- /dev/null +++ b/img/blue-info.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 3cb6744e..b415557c 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,7 @@ + @@ -155,6 +156,11 @@
+
+ Empty view +

This view has no elements

+
+ diff --git a/js/lib/messages.js b/js/lib/messages.js index 4b24f73e..0ce67757 100644 --- a/js/lib/messages.js +++ b/js/lib/messages.js @@ -11,3 +11,11 @@ export function errorMsg(msg) { msgDiv.style.color = "red"; msgDiv.innerHTML = "

ERROR: " + msg + "

"; } + +export function emptyViewMessage() { + const msgDiv = document.getElementById("empty-view"); + msgDiv.style.display = "flex"; + setTimeout(() => { + msgDiv.style.display = "none"; + }, 3000); +} From e29441d8f07c8005ce99cb6af6aa04f2f49d4c93 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 15:41:39 -0500 Subject: [PATCH 38/60] show info modal when empty --- js/views/clustertree.js | 5 ----- js/views/list.js | 3 ++- js/views/mcparticletree.js | 4 +++- js/views/onewayview.js | 3 ++- js/views/recoclustertrack.js | 3 ++- js/views/recoparticletree.js | 4 ---- js/views/tracktree.js | 5 ----- js/views/tree.js | 6 ++++++ 8 files changed, 15 insertions(+), 18 deletions(-) diff --git a/js/views/clustertree.js b/js/views/clustertree.js index f4ce1ca9..e38432c3 100644 --- a/js/views/clustertree.js +++ b/js/views/clustertree.js @@ -5,11 +5,6 @@ export function clusterTree(viewCurrentObjects) { const clusterCollection = viewCurrentObjects.datatypes["edm4hep::Cluster"].collection ?? []; - if (clusterCollection.length === 0) { - alert("No Clusters found in this event."); - return; - } - buildTree(clusterCollection, "clusters"); } diff --git a/js/views/list.js b/js/views/list.js index 99aa7ce4..d364f09d 100644 --- a/js/views/list.js +++ b/js/views/list.js @@ -1,8 +1,9 @@ import { canvas } from "../main.js"; +import { emptyViewMessage } from "../lib/messages.js"; export function listView(collection) { if (collection.length === 0) { - alert("No objects found!"); + emptyViewMessage(); return; } const width = window.innerWidth; diff --git a/js/views/mcparticletree.js b/js/views/mcparticletree.js index 4b702cc1..74aaf1ea 100644 --- a/js/views/mcparticletree.js +++ b/js/views/mcparticletree.js @@ -1,12 +1,14 @@ import { canvas } from "../main.js"; import { preFilterTree } from "./pre-filter.js"; +import { emptyViewMessage } from "../lib/messages.js"; export function mcParticleTree(viewCurrentObjects) { const mcCollection = viewCurrentObjects.datatypes["edm4hep::MCParticle"].collection ?? []; if (mcCollection.length === 0) { - alert("No MCParticles found in this event."); + emptyViewMessage(); + return; } const getMaxRow = (parentLinks) => { diff --git a/js/views/onewayview.js b/js/views/onewayview.js index 1a6f8683..7e02d2d0 100644 --- a/js/views/onewayview.js +++ b/js/views/onewayview.js @@ -1,4 +1,5 @@ import { canvas } from "../main.js"; +import { emptyViewMessage } from "../lib/messages.js"; export function oneWayView(viewObjects, fromCollectionName, relationName) { const relations = @@ -8,7 +9,7 @@ export function oneWayView(viewObjects, fromCollectionName, relationName) { const toCollection = relations.map((relation) => relation.to); if (fromCollection.length === 0 || toCollection.length === 0) { - alert("No association found!"); + emptyViewMessage(); return; } diff --git a/js/views/recoclustertrack.js b/js/views/recoclustertrack.js index 874f9630..192d988f 100644 --- a/js/views/recoclustertrack.js +++ b/js/views/recoclustertrack.js @@ -1,12 +1,13 @@ import { canvas } from "../main.js"; import { emptyCopyObject } from "../lib/copy.js"; +import { emptyViewMessage } from "../lib/messages.js"; export function recoClusterTrackVertex(viewObjects) { const recoParticles = viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection; if (recoParticles.length === 0) { - alert("No reconstructed particles found!"); + emptyViewMessage(); return; } diff --git a/js/views/recoparticletree.js b/js/views/recoparticletree.js index 26a73e32..a578a90a 100644 --- a/js/views/recoparticletree.js +++ b/js/views/recoparticletree.js @@ -6,10 +6,6 @@ export function recoParticleTree(viewCurrentObjects) { viewCurrentObjects.datatypes["edm4hep::ReconstructedParticle"].collection ?? []; - if (recoCollection.length === 0) { - alert("No ReconstructedParticles found in this event."); - } - buildTree(recoCollection, "particles"); } diff --git a/js/views/tracktree.js b/js/views/tracktree.js index 523441b8..6ff0ff36 100644 --- a/js/views/tracktree.js +++ b/js/views/tracktree.js @@ -5,11 +5,6 @@ export function trackTree(viewCurrentObjects) { const trackCollection = viewCurrentObjects.datatypes["edm4hep::Track"].collection ?? []; - if (trackCollection.length === 0) { - alert("No Tracks found in this event."); - return; - } - buildTree(trackCollection, "tracks"); } diff --git a/js/views/tree.js b/js/views/tree.js index bfe89342..45604abb 100644 --- a/js/views/tree.js +++ b/js/views/tree.js @@ -1,10 +1,16 @@ import { canvas } from "../main.js"; +import { emptyViewMessage } from "../lib/messages.js"; // All particles that are related to itself have an one to many relation export function buildTree(collection, relationOfReference) { const nodes = new Set(); const children = new Set(); + if (collection.length === 0) { + emptyViewMessage(); + return; + } + for (const object of collection) { const objects = object.oneToManyRelations[relationOfReference].map( (link) => link.to From d79f237f2250211af31a8ecedfcd8c8705bfc5e1 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 15:44:46 -0500 Subject: [PATCH 39/60] change duration of input modal + show on missing views --- js/lib/messages.js | 2 +- js/views/association-view.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/js/lib/messages.js b/js/lib/messages.js index 0ce67757..310352f4 100644 --- a/js/lib/messages.js +++ b/js/lib/messages.js @@ -17,5 +17,5 @@ export function emptyViewMessage() { msgDiv.style.display = "flex"; setTimeout(() => { msgDiv.style.display = "none"; - }, 3000); + }, 1500); } diff --git a/js/views/association-view.js b/js/views/association-view.js index 5c931c04..249d58c7 100644 --- a/js/views/association-view.js +++ b/js/views/association-view.js @@ -1,4 +1,5 @@ import { canvas } from "../main.js"; +import { emptyViewMessage } from "../lib/messages.js"; // List 1:1 association in a vertical list export function buildAssociationView(viewObjects, associationName) { @@ -6,7 +7,7 @@ export function buildAssociationView(viewObjects, associationName) { const length = associations.length; if (length === 0) { - alert("No association found!"); + emptyViewMessage(); return; } From b18a5777d0da4741aff9015497af239f84b525ca Mon Sep 17 00:00:00 2001 From: Juraj Smiesko Date: Sat, 13 Jul 2024 00:05:23 +0200 Subject: [PATCH 40/60] Adding full sim example file --- index.html | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 3cb6744e..4988ef68 100644 --- a/index.html +++ b/index.html @@ -34,8 +34,15 @@
-

Example input file (right click to save): p8_ee_ZH_ecm240.edm4hep.json +

Example input files (right click to save): +

@@ -164,4 +171,4 @@ - \ No newline at end of file + From 4d051b0f0965d13d037f10a6839c7fa6e290795b Mon Sep 17 00:00:00 2001 From: Juraj Smiesko Date: Sat, 13 Jul 2024 00:13:36 +0200 Subject: [PATCH 41/60] Adding file sizes --- index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index 4988ef68..41f5e7cc 100644 --- a/index.html +++ b/index.html @@ -35,12 +35,12 @@

Example input files (right click to save): -

    +

    From 2ab2e89a680d8f0bdbd8f96a9ad9fcf642627e06 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 13 Jul 2024 18:44:23 -0500 Subject: [PATCH 42/60] improve functionality when checking for empty view --- js/draw.js | 18 +++++++++++++----- js/lib/empty-object.js | 32 ++++++++++++++++++++++++++++++++ js/lib/graphic-primitives.js | 4 ++++ js/lib/messages.js | 8 +++++--- js/views/association-view.js | 6 ------ js/views/list.js | 5 ----- js/views/mcparticletree.js | 6 ------ js/views/onewayview.js | 6 ------ js/views/recoclustertrack.js | 6 ------ js/views/tree.js | 6 ------ js/views/views.js | 13 +++++++++++++ 11 files changed, 67 insertions(+), 43 deletions(-) create mode 100644 js/lib/empty-object.js diff --git a/js/draw.js b/js/draw.js index e5c31903..555d8173 100644 --- a/js/draw.js +++ b/js/draw.js @@ -1,4 +1,5 @@ import { canvas, ctx } from "./main.js"; +import { updateCanvas } from "./lib/graphic-primitives.js"; function draw(objects) { const datatypes = objects.datatypes; @@ -32,19 +33,26 @@ function draw(objects) { } export function drawAll(loadedObjects) { - ctx.clearRect(0, 0, canvas.width, canvas.height); - + emptyCanvas(); draw(loadedObjects); } export function drawVisible(visibleObjects) { + emptyVisibleCanvas(); + draw(visibleObjects); +} + +export function emptyCanvas() { + updateCanvas(ctx, 0, 0, canvas.width, canvas.height); +} + +function emptyVisibleCanvas() { const boundigClientRect = canvas.getBoundingClientRect(); - ctx.clearRect( + updateCanvas( + ctx, 0 - boundigClientRect.x, 0 - boundigClientRect.y, window.innerWidth, window.innerHeight ); - - draw(visibleObjects); } diff --git a/js/lib/empty-object.js b/js/lib/empty-object.js new file mode 100644 index 00000000..8b24aecd --- /dev/null +++ b/js/lib/empty-object.js @@ -0,0 +1,32 @@ +const updateEmpty = (empty, length) => { + if (length === 0) { + empty.value = empty.value && true; + } else { + empty.value = false; + } +}; + +export function checkEmptyObject(obj) { + const datatypes = obj.datatypes; + const associations = obj.associations; + + let empty = { value: true }; + + Object.values(datatypes).forEach((datatype) => { + updateEmpty(empty, datatype.collection.length); + + Object.values(datatype.oneToMany).forEach((oneToMany) => { + updateEmpty(empty, oneToMany.length); + }); + + Object.values(datatype.oneToOne).forEach((oneToOne) => { + updateEmpty(empty, oneToOne.length); + }); + }); + + Object.values(associations).forEach((association) => { + updateEmpty(empty, association.length); + }); + + return empty.value; +} diff --git a/js/lib/graphic-primitives.js b/js/lib/graphic-primitives.js index 2e3438f5..248b9498 100644 --- a/js/lib/graphic-primitives.js +++ b/js/lib/graphic-primitives.js @@ -107,3 +107,7 @@ export function drawStraightLink(ctx, link) { ctx.stroke(); ctx.restore(); } + +export function updateCanvas(ctx, x, y, width, height) { + ctx.clearRect(x, y, width, height); +} diff --git a/js/lib/messages.js b/js/lib/messages.js index 310352f4..495bb3b9 100644 --- a/js/lib/messages.js +++ b/js/lib/messages.js @@ -15,7 +15,9 @@ export function errorMsg(msg) { export function emptyViewMessage() { const msgDiv = document.getElementById("empty-view"); msgDiv.style.display = "flex"; - setTimeout(() => { - msgDiv.style.display = "none"; - }, 1500); +} + +export function hideEmptyViewMessage() { + const msgDiv = document.getElementById("empty-view"); + msgDiv.style.display = "none"; } diff --git a/js/views/association-view.js b/js/views/association-view.js index 249d58c7..4f04bf03 100644 --- a/js/views/association-view.js +++ b/js/views/association-view.js @@ -1,16 +1,10 @@ import { canvas } from "../main.js"; -import { emptyViewMessage } from "../lib/messages.js"; // List 1:1 association in a vertical list export function buildAssociationView(viewObjects, associationName) { const associations = viewObjects.associations[associationName]; const length = associations.length; - if (length === 0) { - emptyViewMessage(); - return; - } - const fromWidth = associations[0].from.width; const toWidth = associations[0].to.width; const fromHorizontalGap = 0.3 * fromWidth; diff --git a/js/views/list.js b/js/views/list.js index d364f09d..a6806249 100644 --- a/js/views/list.js +++ b/js/views/list.js @@ -1,11 +1,6 @@ import { canvas } from "../main.js"; -import { emptyViewMessage } from "../lib/messages.js"; export function listView(collection) { - if (collection.length === 0) { - emptyViewMessage(); - return; - } const width = window.innerWidth; canvas.width = width; diff --git a/js/views/mcparticletree.js b/js/views/mcparticletree.js index 74aaf1ea..7f4ec1cf 100644 --- a/js/views/mcparticletree.js +++ b/js/views/mcparticletree.js @@ -1,16 +1,10 @@ import { canvas } from "../main.js"; import { preFilterTree } from "./pre-filter.js"; -import { emptyViewMessage } from "../lib/messages.js"; export function mcParticleTree(viewCurrentObjects) { const mcCollection = viewCurrentObjects.datatypes["edm4hep::MCParticle"].collection ?? []; - if (mcCollection.length === 0) { - emptyViewMessage(); - return; - } - const getMaxRow = (parentLinks) => { let maxRow = -1; for (const parentLink of parentLinks) { diff --git a/js/views/onewayview.js b/js/views/onewayview.js index 7e02d2d0..c69bddaa 100644 --- a/js/views/onewayview.js +++ b/js/views/onewayview.js @@ -1,5 +1,4 @@ import { canvas } from "../main.js"; -import { emptyViewMessage } from "../lib/messages.js"; export function oneWayView(viewObjects, fromCollectionName, relationName) { const relations = @@ -8,11 +7,6 @@ export function oneWayView(viewObjects, fromCollectionName, relationName) { const fromCollection = relations.map((relation) => relation.from); const toCollection = relations.map((relation) => relation.to); - if (fromCollection.length === 0 || toCollection.length === 0) { - emptyViewMessage(); - return; - } - const fromWidth = fromCollection[0].width; const toWidth = toCollection[0].width; const fromHorizontalGap = 0.3 * fromWidth; diff --git a/js/views/recoclustertrack.js b/js/views/recoclustertrack.js index 192d988f..d9fd30ea 100644 --- a/js/views/recoclustertrack.js +++ b/js/views/recoclustertrack.js @@ -1,16 +1,10 @@ import { canvas } from "../main.js"; import { emptyCopyObject } from "../lib/copy.js"; -import { emptyViewMessage } from "../lib/messages.js"; export function recoClusterTrackVertex(viewObjects) { const recoParticles = viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection; - if (recoParticles.length === 0) { - emptyViewMessage(); - return; - } - const findFirstObject = (relationName) => { const object = recoParticles.find((particle) => { const relation = particle.oneToManyRelations[relationName]; diff --git a/js/views/tree.js b/js/views/tree.js index 45604abb..bfe89342 100644 --- a/js/views/tree.js +++ b/js/views/tree.js @@ -1,16 +1,10 @@ import { canvas } from "../main.js"; -import { emptyViewMessage } from "../lib/messages.js"; // All particles that are related to itself have an one to many relation export function buildTree(collection, relationOfReference) { const nodes = new Set(); const children = new Set(); - if (collection.length === 0) { - emptyViewMessage(); - return; - } - for (const object of collection) { const objects = object.oneToManyRelations[relationOfReference].map( (link) => link.to diff --git a/js/views/views.js b/js/views/views.js index ce655a2a..d40beadd 100644 --- a/js/views/views.js +++ b/js/views/views.js @@ -1,5 +1,6 @@ import { currentObjects, currentEvent } from "../event-number.js"; import { copyObject } from "../lib/copy.js"; +import { checkEmptyObject } from "../lib/empty-object.js"; import { getVisible } from "../events.js"; import { drawAll } from "../draw.js"; import { canvas } from "../main.js"; @@ -11,6 +12,8 @@ import { mouseMove, onScroll, } from "../events.js"; +import { emptyViewMessage, hideEmptyViewMessage } from "../lib/messages.js"; +import { emptyCanvas } from "../draw.js"; const currentView = {}; @@ -55,6 +58,16 @@ const drawView = (view) => { const viewVisibleObjects = {}; preFilterFunction(currentObjects, viewObjects); + const isEmpty = checkEmptyObject(viewObjects); + + if (isEmpty) { + emptyCanvas(); + emptyViewMessage(); + return; + } else { + hideEmptyViewMessage(); + } + viewFunction(viewObjects); copyObject(viewObjects, viewCurrentObjects); From 3c459903f7905cb14605cafd95b50ed0528f08d9 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 13 Jul 2024 18:56:06 -0500 Subject: [PATCH 43/60] change cursor on mouse over --- js/events.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/events.js b/js/events.js index 0c300be8..8eb2e169 100644 --- a/js/events.js +++ b/js/events.js @@ -61,11 +61,13 @@ const mouseMove = function (event, visibleObjects, dragTools) { object.showObjectTip(ctx); } someHovered = true; + document.body.style.cursor = "pointer"; break; } } if (!someHovered) { + document.body.style.cursor = "default"; dragTools.hoveredObject = null; drawVisible(visibleObjects); } From 4910729360be78e88dca9e37629738be00b18553 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 13 Jul 2024 19:25:47 -0500 Subject: [PATCH 44/60] simplify if statement --- js/views/views.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/views/views.js b/js/views/views.js index d40beadd..f74cb3ba 100644 --- a/js/views/views.js +++ b/js/views/views.js @@ -64,9 +64,8 @@ const drawView = (view) => { emptyCanvas(); emptyViewMessage(); return; - } else { - hideEmptyViewMessage(); } + hideEmptyViewMessage(); viewFunction(viewObjects); copyObject(viewObjects, viewCurrentObjects); From 05371ccf52841c927063eab4653cdf5bdfddd9d1 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 13 Jul 2024 20:21:48 -0500 Subject: [PATCH 45/60] show basic informationabout the current view inside the modal --- css/empty-view.css | 2 +- css/information.css | 33 +++++++++++ index.html | 110 +++++++++++++++++++---------------- js/information.js | 51 ++++++++++++++++ js/views/views-dictionary.js | 17 ++++++ js/views/views.js | 13 ++++- 6 files changed, 173 insertions(+), 53 deletions(-) diff --git a/css/empty-view.css b/css/empty-view.css index da3d2073..5b728058 100644 --- a/css/empty-view.css +++ b/css/empty-view.css @@ -4,7 +4,7 @@ background-color: #e1e1e1; padding: 10px; position: fixed; - z-index: 2; + z-index: 1; height: 30px; top: 50%; left: 50%; diff --git a/css/information.css b/css/information.css index 6f02a227..951496b7 100644 --- a/css/information.css +++ b/css/information.css @@ -37,3 +37,36 @@ .info-modal-content { width: 100%; } + +#information-buttons { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + margin-top: 10px; +} + +.information-button { + background-color: #fff; + padding: 5px; + border-radius: 5px; + border: 1px solid black; + margin: 0 5px; +} + +.information-button:hover { + cursor: pointer; + background-color: #c5c5c5; +} + +#information-button { + background-color: #c5c5c5; +} + +#view-information-button { + display: none; +} + +#view-information-content { + display: none; +} diff --git a/index.html b/index.html index b415557c..ca365ab0 100644 --- a/index.html +++ b/index.html @@ -99,55 +99,67 @@
    eedE -

    Welcome to eede, an EDM4hep Event Data Explorer. Learn more about eede on the wiki. - Want to learn more about EDM4hep? Check out the EDM4hep website. -

    -

    - Found a bug or have a feature request? Open a new issue. -

    -

    Contact:

    -
      -
    • Juraj Smiesko: - - - -
    • -
    • Thomas Madlener: - - -
    • -
    • Braulio Rivas: - - -
    • -
    +
    + + +
    +
    +
    +

    Welcome to eede, an EDM4hep Event Data Explorer. Learn more about eede on the wiki. + Want to learn more about EDM4hep? Check out the EDM4hep website. +

    +

    + Found a bug or have a feature request? Open a new issue. +

    +

    Contact:

    +
      +
    • Juraj Smiesko: + + + +
    • +
    • Thomas Madlener: + + +
    • +
    • Braulio Rivas: + + +
    • +
    +
    +
    +

    +

    +
    +
diff --git a/js/information.js b/js/information.js index d26168cc..53a820db 100644 --- a/js/information.js +++ b/js/information.js @@ -2,6 +2,8 @@ const infoIcon = document.getElementById("information-icon"); const closeIcon = document.getElementById("close-information"); const copyToClipboardButtons = document.getElementsByClassName("copy-email-button"); +const informationButton = document.getElementById("information-button"); +const viewButton = document.getElementById("view-information-button"); Array.from(copyToClipboardButtons).forEach((button) => { button.addEventListener("click", () => { @@ -56,3 +58,52 @@ window.addEventListener("click", (event) => { hideModal(); } }); + +function chooseButton(id) { + const buttons = document.getElementsByClassName("information-button"); + Array.from(buttons).forEach((button) => { + if (button.id === id) { + button.style.backgroundColor = "#c5c5c5"; + } else { + button.style.backgroundColor = "#ffffff"; + } + }); +} + +function showOption(id) { + const informationOptions = document.getElementById("information-options"); + const children = informationOptions.children; + Array.from(children).forEach((child) => { + if (child.id === id) { + child.style.display = "block"; + } else { + child.style.display = "none"; + } + }); +} + +informationButton.addEventListener("click", () => { + chooseButton("information-button"); + showOption("information-content"); +}); + +viewButton.addEventListener("click", () => { + chooseButton("view-information-button"); + showOption("view-information-content"); +}); + +export function showViewInformation(title, description) { + if (viewButton.style.display !== "block") { + viewButton.style.display = "block"; + } + + const viewTitle = document.getElementById("view-title-info"); + viewTitle.innerText = `Learn more about ${title} view`; + + const viewDescription = document.getElementById("view-description-info"); + viewDescription.innerText = description; +} + +export function hideViewInformation() { + viewButton.style.display = "none"; +} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 03112d6d..9496be07 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -25,65 +25,82 @@ export const views = { viewFunction: mcParticleTree, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCTree, + description: + "Red relations mean parent relation (from bottom to top), green relations mean daughter relation (from top to bottom).", }, "Reconstructed Particle Tree": { filters: setupNoFilter, viewFunction: recoParticleTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterRecoTree, + description: + "A tree of the Reconstructed Particles. Purple relations mean relation between particles.", }, "Track Tree": { filters: setupNoFilter, viewFunction: trackTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterTrackTree, + description: "A tree of the Tracks.", }, "Cluster Tree": { filters: setupNoFilter, viewFunction: clusterTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterClusterTree, + description: "A tree of the Clusters.", }, "RecoParticle-Cluster-Track-Vertex": { filters: setupNoFilter, viewFunction: recoClusterTrackVertex, scrollFunction: scrollTopCenter, preFilterFunction: preFilterRecoClusterTrackVertex, + description: + "Relations that a Reconstruced Particle has with other objects. Green connections are towards Tracks, and sky blue connections are towards Clusters.", }, "Monte Carlo-Reconstructed Particle": { filters: setupNoFilter, viewFunction: mcRecoAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCReco, + description: + "Association between Monte Carlo Particles and Reconstructed Particles. 1:1 relation.", }, "Monte Carlo Particle-Track": { filters: setupNoFilter, viewFunction: mcTrackAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCTrack, + description: + "Association between Monte Carlo Particles and Tracks. 1:1 relation.", }, "Monte Carlo Particle-Cluster": { filters: setupNoFilter, viewFunction: mcClusterAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCCluster, + description: + "Association between Monte Carlo Particles and Clusters. 1:1 relation.", }, "ParticleID List": { filters: setupNoFilter, viewFunction: particleIDList, scrollFunction: scrollTopLeft, preFilterFunction: preFilterParticleIDList, + description: "A list of ParticleIDs found in the event.", }, "Vertex List": { filters: setupNoFilter, viewFunction: vertexList, scrollFunction: scrollTopLeft, preFilterFunction: preFilterVertexList, + description: "A list of Vertices found in the event.", }, "ParticleID-Reconstructed Particle": { filters: setupNoFilter, viewFunction: recoParticleID, scrollFunction: scrollTopCenter, preFilterFunction: preFilterRecoParticleID, + description: "1:1 relation from ParticleID to Reconstructed Particle.", }, }; diff --git a/js/views/views.js b/js/views/views.js index f74cb3ba..2b61b1ac 100644 --- a/js/views/views.js +++ b/js/views/views.js @@ -13,6 +13,7 @@ import { onScroll, } from "../events.js"; import { emptyViewMessage, hideEmptyViewMessage } from "../lib/messages.js"; +import { showViewInformation, hideViewInformation } from "../information.js"; import { emptyCanvas } from "../draw.js"; const currentView = {}; @@ -50,8 +51,13 @@ const drawView = (view) => { prevMouseY: 0, }; - const { preFilterFunction, viewFunction, scrollFunction, filters } = - views[view]; + const { + preFilterFunction, + viewFunction, + scrollFunction, + filters, + description, + } = views[view]; const viewObjects = {}; const viewCurrentObjects = {}; @@ -63,10 +69,11 @@ const drawView = (view) => { if (isEmpty) { emptyCanvas(); emptyViewMessage(); + hideViewInformation(); return; } + showViewInformation(view, description); hideEmptyViewMessage(); - viewFunction(viewObjects); copyObject(viewObjects, viewCurrentObjects); From 92cc302c66e0bd78c518488698bd6fe1afaf1aaa Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 13 Jul 2024 21:08:24 -0500 Subject: [PATCH 46/60] display simulator status for MCParticle on mouse over together with collection --- js/lib/graphic-primitives.js | 15 ++++++++++----- js/menu/filter/filter.js | 2 +- js/types/objects.js | 23 ++++++++++++++++++++++- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/js/lib/graphic-primitives.js b/js/lib/graphic-primitives.js index 5335f754..a09cf63f 100644 --- a/js/lib/graphic-primitives.js +++ b/js/lib/graphic-primitives.js @@ -132,12 +132,17 @@ export function drawObjectHeader(ctx, object) { ctx.restore(); } -export function drawObjectInfoTip(ctx, object) { +export function drawObjectInfoTip(ctx, x, y, ...args) { ctx.save(); - const collectionName = "Collection: " + object.collectionName; - const x = object.x; - const y = object.y - 10; ctx.font = "bold 12px sans-serif"; - ctx.fillText(collectionName, x, y); + const lines = args.length; + const height = 20 * lines; + const maxWidth = Math.max(...args.map((arg) => ctx.measureText(arg).width)); + ctx.fillStyle = "rgba(225, 225, 225, 1)"; + ctx.fillRect(x, y, maxWidth + 10, height + 10); + ctx.fillStyle = "black"; + for (const [i, arg] of args.entries()) { + ctx.fillText(arg, x + 5, y + 20 + i * 20); + } ctx.restore(); } diff --git a/js/menu/filter/filter.js b/js/menu/filter/filter.js index 60724f6a..7938140e 100644 --- a/js/menu/filter/filter.js +++ b/js/menu/filter/filter.js @@ -60,7 +60,7 @@ let parametersRange = units.sort((a, b) => parametersRange = parametersRange.map((parameter) => new Range(parameter)); -const SimStatusBitFieldDisplayValues = { +export const SimStatusBitFieldDisplayValues = { 23: "Overlay", 24: "Stopped", 25: "LeftDetector", diff --git a/js/types/objects.js b/js/types/objects.js index 9875884b..1df1ea2f 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -8,6 +8,7 @@ import { import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; import { parseCharge } from "../lib/parseCharge.js"; +import { SimStatusBitFieldDisplayValues } from "../menu/filter/filter.js"; const TOP_MARGIN = 40; @@ -56,7 +57,10 @@ class EDMObject { } showObjectTip(ctx) { - drawObjectInfoTip(ctx, this); + const x = this.x; + const y = this.y - 10; + const collectionName = "Collection: " + this.collectionName; + drawObjectInfoTip(ctx, x, y, collectionName); } } @@ -125,6 +129,23 @@ export class MCParticle extends EDMObject { drawTextLines(ctx, bottomLines, boxCenterX, bottomY, 22); } + showObjectTip(ctx) { + const x = this.x; + const y = this.y - 10; + const collectionName = "Collection: " + this.collectionName; + const displaySimulatorStatus = + SimStatusBitFieldDisplayValues[this.simulatorStatus]; + let simulatorStatus = ""; + + if (displaySimulatorStatus) { + simulatorStatus = "Simulator status: " + displaySimulatorStatus; + } else { + simulatorStatus = "Simulator status: " + this.simulatorStatus; + } + + drawObjectInfoTip(ctx, x, y, collectionName, simulatorStatus); + } + static setup(mcCollection) { for (const mcParticle of mcCollection) { const parentLength = mcParticle.oneToManyRelations["parents"].length; From 1fcfeabbc79c664ea3497fb4ab7170bb79c9997f Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 13 Jul 2024 21:23:22 -0500 Subject: [PATCH 47/60] move mappings from SimStatus into new file to fix tests --- js/lib/getName.js | 2 +- js/menu/filter/filter.js | 12 +----------- js/types/objects.js | 2 +- {data => mappings}/particles.js | 0 mappings/sim-status.js | 10 ++++++++++ 5 files changed, 13 insertions(+), 13 deletions(-) rename {data => mappings}/particles.js (100%) create mode 100644 mappings/sim-status.js diff --git a/js/lib/getName.js b/js/lib/getName.js index edfbc1f5..599fb792 100644 --- a/js/lib/getName.js +++ b/js/lib/getName.js @@ -1,4 +1,4 @@ -import { mappings } from "../../data/particles.js"; +import { mappings } from "../../mappings/particles.js"; export function getName(pdg) { const particle = mappings[pdg]; diff --git a/js/menu/filter/filter.js b/js/menu/filter/filter.js index 7938140e..abcc5807 100644 --- a/js/menu/filter/filter.js +++ b/js/menu/filter/filter.js @@ -5,6 +5,7 @@ import { reconnect } from "./reconnect.js"; import { getVisible } from "../../events.js"; import { units } from "../../types/units.js"; import { copyObject } from "../../lib/copy.js"; +import { SimStatusBitFieldDisplayValues } from "../../../mappings/sim-status.js"; const filterButton = document.getElementById("filter-button"); const openFilter = document.getElementById("open-filter"); @@ -60,17 +61,6 @@ let parametersRange = units.sort((a, b) => parametersRange = parametersRange.map((parameter) => new Range(parameter)); -export const SimStatusBitFieldDisplayValues = { - 23: "Overlay", - 24: "Stopped", - 25: "LeftDetector", - 26: "DecayedInCalorimeter", - 27: "DecayedInTracker", - 28: "VertexIsNotEndpointOfParent", - 29: "Backscatter", - 30: "CreatedInSimulation", -}; - const bits = new BitFieldBuilder( "simulatorStatus", "Simulation status", diff --git a/js/types/objects.js b/js/types/objects.js index 1df1ea2f..b9b7dc3b 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -8,7 +8,7 @@ import { import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; import { parseCharge } from "../lib/parseCharge.js"; -import { SimStatusBitFieldDisplayValues } from "../menu/filter/filter.js"; +import { SimStatusBitFieldDisplayValues } from "../../mappings/sim-status.js"; const TOP_MARGIN = 40; diff --git a/data/particles.js b/mappings/particles.js similarity index 100% rename from data/particles.js rename to mappings/particles.js diff --git a/mappings/sim-status.js b/mappings/sim-status.js new file mode 100644 index 00000000..49727790 --- /dev/null +++ b/mappings/sim-status.js @@ -0,0 +1,10 @@ +export const SimStatusBitFieldDisplayValues = { + 23: "Overlay", + 24: "Stopped", + 25: "LeftDetector", + 26: "DecayedInCalorimeter", + 27: "DecayedInTracker", + 28: "VertexIsNotEndpointOfParent", + 29: "Backscatter", + 30: "CreatedInSimulation", +}; From c8ad0fdc2054c6f249cac58343f3735b01079f6a Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Thu, 11 Jul 2024 19:33:10 -0500 Subject: [PATCH 48/60] add switcher between release and main --- css/switch-deploy.css | 23 +++++++++++++++++++++++ index.html | 6 ++++++ js/switch-deploy.js | 22 ++++++++++++++++++++++ 3 files changed, 51 insertions(+) create mode 100644 css/switch-deploy.css create mode 100644 js/switch-deploy.js diff --git a/css/switch-deploy.css b/css/switch-deploy.css new file mode 100644 index 00000000..5f496930 --- /dev/null +++ b/css/switch-deploy.css @@ -0,0 +1,23 @@ +#switch-deploy { + display: flex; + flex-direction: row; + align-items: center; +} + +#switch-deploy-button { + cursor: pointer; + background-color: #fff; + border: 1px solid #000; + padding: 5px; + border-radius: 5px; + font-family: sans-serif; + font-size: 14px; +} + +#switch-deploy-button:hover { + background-color: #c5c5c5; +} + +#switch-deploy-text { + margin: 0 10px 0 0; +} diff --git a/index.html b/index.html index 3cb6744e..700ccfbf 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,7 @@ + @@ -148,6 +149,10 @@ +
+

Switch to

+ +
@@ -162,6 +167,7 @@ + \ No newline at end of file diff --git a/js/switch-deploy.js b/js/switch-deploy.js new file mode 100644 index 00000000..445ae82b --- /dev/null +++ b/js/switch-deploy.js @@ -0,0 +1,22 @@ +const button = document.getElementById("switch-deploy-button"); + +button.addEventListener("click", () => { + const currentUrl = window.location.href; + + if (currentUrl.includes("/main")) { + window.location.href = currentUrl.replace("/main", "/release"); + } else if (currentUrl.includes("/release")) { + window.location.href = currentUrl.replace("/release", "/main"); + } else { + window.location.href = "https://key4hep.github.io/eede/release/index.html"; + } +}); + +const url = window.location.href; +if (url.includes("/main")) { + button.innerText = "Release"; +} else if (url.includes("/release")) { + button.innerText = "Main"; +} else { + button.innerText = "Release"; +} From a588bfdde27fc58b2cc545e142d596290d7b8f94 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Fri, 12 Jul 2024 10:19:02 -0500 Subject: [PATCH 49/60] show switcher on home page instead of modal --- css/switch-deploy.css | 11 ++++++++++- index.html | 9 +++++---- js/main.js | 7 +++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/css/switch-deploy.css b/css/switch-deploy.css index 5f496930..dea8a671 100644 --- a/css/switch-deploy.css +++ b/css/switch-deploy.css @@ -1,7 +1,15 @@ #switch-deploy { + position: fixed; + left: 10px; + bottom: 10px; display: flex; flex-direction: row; align-items: center; + z-index: 1; + background-color: #fff; + padding: 5px; + border-radius: 5px; + border: 1px solid #000; } #switch-deploy-button { @@ -16,8 +24,9 @@ #switch-deploy-button:hover { background-color: #c5c5c5; + cursor: pointer; } #switch-deploy-text { - margin: 0 10px 0 0; + margin: 0 7px 0 0; } diff --git a/index.html b/index.html index 700ccfbf..48d7fdab 100644 --- a/index.html +++ b/index.html @@ -149,10 +149,6 @@
-
-

Switch to

- -
@@ -160,6 +156,11 @@
+
+

Switch to

+ +
+ diff --git a/js/main.js b/js/main.js index e9ab8b74..2dd779e9 100644 --- a/js/main.js +++ b/js/main.js @@ -43,6 +43,12 @@ function showViewsMenu() { viewsMenu.style.display = "flex"; } +function hideDeploySwitch() { + const deploySwitch = document.getElementById("switch-deploy"); + + deploySwitch.style.display = "none"; +} + document.getElementById("input-file").addEventListener("change", (event) => { for (const file of event.target.files) { if (!file.name.endsWith("edm4hep.json")) { @@ -135,6 +141,7 @@ document showEventSwitcher(); showViewsMenu(); renderEvent(eventNum); + hideDeploySwitch(); }); export { canvas, ctx, jsonData, selectedObjectTypes }; From b9d7f732ec12000f21991dce6526436ddc193c82 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 15 Jul 2024 17:10:38 -0500 Subject: [PATCH 50/60] simplify if statement --- js/switch-deploy.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/js/switch-deploy.js b/js/switch-deploy.js index 445ae82b..d69e70b3 100644 --- a/js/switch-deploy.js +++ b/js/switch-deploy.js @@ -3,9 +3,7 @@ const button = document.getElementById("switch-deploy-button"); button.addEventListener("click", () => { const currentUrl = window.location.href; - if (currentUrl.includes("/main")) { - window.location.href = currentUrl.replace("/main", "/release"); - } else if (currentUrl.includes("/release")) { + if (currentUrl.includes("/release")) { window.location.href = currentUrl.replace("/release", "/main"); } else { window.location.href = "https://key4hep.github.io/eede/release/index.html"; @@ -13,9 +11,7 @@ button.addEventListener("click", () => { }); const url = window.location.href; -if (url.includes("/main")) { - button.innerText = "Release"; -} else if (url.includes("/release")) { +if (url.includes("/release")) { button.innerText = "Main"; } else { button.innerText = "Release"; From 5f881c46eccb0bc244f0af205b4e60aa0b0738a1 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 15 Jul 2024 17:12:31 -0500 Subject: [PATCH 51/60] rename main to develop --- js/switch-deploy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/switch-deploy.js b/js/switch-deploy.js index d69e70b3..24fb9009 100644 --- a/js/switch-deploy.js +++ b/js/switch-deploy.js @@ -12,7 +12,7 @@ button.addEventListener("click", () => { const url = window.location.href; if (url.includes("/release")) { - button.innerText = "Main"; + button.innerText = "Develop"; } else { button.innerText = "Release"; } From 28b2d709bcd518aec0a60a702e29e31df1d54ba4 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 15 Jul 2024 17:45:39 -0500 Subject: [PATCH 52/60] parse bitfield according to @tmadlener bitwise operations --- js/types/objects.js | 28 ++++++++++++++++------------ mappings/sim-status.js | 24 ++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/js/types/objects.js b/js/types/objects.js index b9b7dc3b..4f9c90ff 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -8,7 +8,7 @@ import { import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; import { parseCharge } from "../lib/parseCharge.js"; -import { SimStatusBitFieldDisplayValues } from "../../mappings/sim-status.js"; +import { getSimStatusDisplayValuesFromBit } from "../../mappings/sim-status.js"; const TOP_MARGIN = 40; @@ -114,7 +114,17 @@ export class MCParticle extends EDMObject { const topLines = []; topLines.push("ID: " + this.index); topLines.push("Gen. stat.: " + this.generatorStatus); - topLines.push("Sim. stat.: " + this.simulatorStatus); + const simulatorStatus = getSimStatusDisplayValuesFromBit( + this.simulatorStatus + ); + const simulatorStatusFirstLetter = simulatorStatus + .map((s) => s[0]) + .join(", "); + const simulatorStatusString = + simulatorStatusFirstLetter !== "" + ? simulatorStatusFirstLetter + : this.simulatorStatus; + topLines.push("Sim. stat.: " + simulatorStatusString); const bottomY = this.y + this.height * 0.6; const bottomLines = []; @@ -133,17 +143,11 @@ export class MCParticle extends EDMObject { const x = this.x; const y = this.y - 10; const collectionName = "Collection: " + this.collectionName; - const displaySimulatorStatus = - SimStatusBitFieldDisplayValues[this.simulatorStatus]; - let simulatorStatus = ""; - - if (displaySimulatorStatus) { - simulatorStatus = "Simulator status: " + displaySimulatorStatus; - } else { - simulatorStatus = "Simulator status: " + this.simulatorStatus; - } + const simulatorStatus = getSimStatusDisplayValuesFromBit( + this.simulatorStatus + ); - drawObjectInfoTip(ctx, x, y, collectionName, simulatorStatus); + drawObjectInfoTip(ctx, x, y, collectionName, ...simulatorStatus); } static setup(mcCollection) { diff --git a/mappings/sim-status.js b/mappings/sim-status.js index 49727790..db438605 100644 --- a/mappings/sim-status.js +++ b/mappings/sim-status.js @@ -8,3 +8,27 @@ export const SimStatusBitFieldDisplayValues = { 29: "Backscatter", 30: "CreatedInSimulation", }; + +export function parseBits(bit) { + const bits = []; + + for (let i = 0; i < 32; i++) { + if (bit & (1 << i)) { + bits.push(i); + } + } + + return bits; +} + +export function getSimStatusDisplayValues(bits) { + return bits.map((bit) => + SimStatusBitFieldDisplayValues[bit] !== undefined + ? SimStatusBitFieldDisplayValues[bit] + : `Bit ${bit}` + ); +} + +export function getSimStatusDisplayValuesFromBit(bit) { + return getSimStatusDisplayValues(parseBits(bit)); +} From 873c4f358e5e659163c3363de8aac2d22139639e Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 15 Jul 2024 18:08:01 -0500 Subject: [PATCH 53/60] select view information by default when visualizing --- js/information.js | 14 +++++++++++--- js/main.js | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/js/information.js b/js/information.js index 53a820db..fcb93a1b 100644 --- a/js/information.js +++ b/js/information.js @@ -82,14 +82,22 @@ function showOption(id) { }); } -informationButton.addEventListener("click", () => { +export function selectInformationSection() { chooseButton("information-button"); showOption("information-content"); -}); +} -viewButton.addEventListener("click", () => { +export function selectViewInformation() { chooseButton("view-information-button"); showOption("view-information-content"); +} + +informationButton.addEventListener("click", () => { + selectInformationSection(); +}); + +viewButton.addEventListener("click", () => { + selectViewInformation(); }); export function showViewInformation(title, description) { diff --git a/js/main.js b/js/main.js index e9ab8b74..e581d726 100644 --- a/js/main.js +++ b/js/main.js @@ -2,6 +2,7 @@ import { errorMsg } from "./lib/messages.js"; import { renderEvent } from "./event-number.js"; import { setView, getView } from "./views/views.js"; import { views } from "./views/views-dictionary.js"; +import { selectViewInformation } from "./information.js"; const canvas = document.getElementById("canvas"); const ctx = canvas.getContext("2d"); @@ -135,6 +136,7 @@ document showEventSwitcher(); showViewsMenu(); renderEvent(eventNum); + selectViewInformation(); }); export { canvas, ctx, jsonData, selectedObjectTypes }; From f67d4803c4e60760812a2591f9b58433f85108f1 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 15 Jul 2024 18:09:07 -0500 Subject: [PATCH 54/60] show first This View button --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index ca365ab0..1df50559 100644 --- a/index.html +++ b/index.html @@ -100,8 +100,8 @@ eedE
- +
From 2a0f9f96a306b536fc44602cbea90b7344294ff0 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 15 Jul 2024 18:16:15 -0500 Subject: [PATCH 55/60] use html for descriptions --- index.html | 2 +- js/information.js | 5 ++++- js/views/views-dictionary.js | 23 ++++++++++++----------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index 1df50559..51117b64 100644 --- a/index.html +++ b/index.html @@ -157,7 +157,7 @@

-

+
diff --git a/js/information.js b/js/information.js index fcb93a1b..d0972c08 100644 --- a/js/information.js +++ b/js/information.js @@ -109,7 +109,10 @@ export function showViewInformation(title, description) { viewTitle.innerText = `Learn more about ${title} view`; const viewDescription = document.getElementById("view-description-info"); - viewDescription.innerText = description; + viewDescription.replaceChildren(); + const newElement = document.createElement("div"); + newElement.innerHTML = description; + viewDescription.appendChild(newElement.firstChild); } export function hideViewInformation() { diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index 9496be07..c5989cd2 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -26,7 +26,7 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCTree, description: - "Red relations mean parent relation (from bottom to top), green relations mean daughter relation (from top to bottom).", + "

Red relations mean parent relation (from bottom to top), green relations mean daughter relation (from top to bottom).

", }, "Reconstructed Particle Tree": { filters: setupNoFilter, @@ -34,21 +34,21 @@ export const views = { scrollFunction: scrollTopLeft, preFilterFunction: preFilterRecoTree, description: - "A tree of the Reconstructed Particles. Purple relations mean relation between particles.", + "

A tree of the Reconstructed Particles. Purple relations mean relation between particles.

", }, "Track Tree": { filters: setupNoFilter, viewFunction: trackTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterTrackTree, - description: "A tree of the Tracks.", + description: "

A tree of the Tracks.

", }, "Cluster Tree": { filters: setupNoFilter, viewFunction: clusterTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterClusterTree, - description: "A tree of the Clusters.", + description: "

A tree of the Clusters.

", }, "RecoParticle-Cluster-Track-Vertex": { filters: setupNoFilter, @@ -56,7 +56,7 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterRecoClusterTrackVertex, description: - "Relations that a Reconstruced Particle has with other objects. Green connections are towards Tracks, and sky blue connections are towards Clusters.", + "

Relations that a Reconstruced Particle has with other objects. Green connections are towards Tracks, and sky blue connections are towards Clusters.

", }, "Monte Carlo-Reconstructed Particle": { filters: setupNoFilter, @@ -64,7 +64,7 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCReco, description: - "Association between Monte Carlo Particles and Reconstructed Particles. 1:1 relation.", + "

Association between Monte Carlo Particles and Reconstructed Particles. 1:1 relation.

", }, "Monte Carlo Particle-Track": { filters: setupNoFilter, @@ -72,7 +72,7 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCTrack, description: - "Association between Monte Carlo Particles and Tracks. 1:1 relation.", + "

Association between Monte Carlo Particles and Tracks. 1:1 relation.

", }, "Monte Carlo Particle-Cluster": { filters: setupNoFilter, @@ -80,27 +80,28 @@ export const views = { scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCCluster, description: - "Association between Monte Carlo Particles and Clusters. 1:1 relation.", + "

Association between Monte Carlo Particles and Clusters. 1:1 relation.

", }, "ParticleID List": { filters: setupNoFilter, viewFunction: particleIDList, scrollFunction: scrollTopLeft, preFilterFunction: preFilterParticleIDList, - description: "A list of ParticleIDs found in the event.", + description: "

A list of ParticleIDs found in the event.

", }, "Vertex List": { filters: setupNoFilter, viewFunction: vertexList, scrollFunction: scrollTopLeft, preFilterFunction: preFilterVertexList, - description: "A list of Vertices found in the event.", + description: "

A list of Vertices found in the event.

", }, "ParticleID-Reconstructed Particle": { filters: setupNoFilter, viewFunction: recoParticleID, scrollFunction: scrollTopCenter, preFilterFunction: preFilterRecoParticleID, - description: "1:1 relation from ParticleID to Reconstructed Particle.", + description: + "

1:1 relation from ParticleID to Reconstructed Particle.

", }, }; From 0281cbd86cc71d4443d7d3e9fd8a24bc8891004a Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 15 Jul 2024 18:26:12 -0500 Subject: [PATCH 56/60] wrap words in span to use html color styling --- js/lib/html-string.js | 3 +++ js/views/views-dictionary.js | 45 +++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 18 deletions(-) create mode 100644 js/lib/html-string.js diff --git a/js/lib/html-string.js b/js/lib/html-string.js new file mode 100644 index 00000000..2d8e2eaf --- /dev/null +++ b/js/lib/html-string.js @@ -0,0 +1,3 @@ +export function spanWithColor(text, color) { + return `${text}`; +} diff --git a/js/views/views-dictionary.js b/js/views/views-dictionary.js index c5989cd2..894b8e14 100644 --- a/js/views/views-dictionary.js +++ b/js/views/views-dictionary.js @@ -18,6 +18,7 @@ import { setupNoFilter } from "../filter/nofilter.js"; import { vertexList, preFilterVertexList } from "./vertexlist.js"; import { particleIDList, preFilterParticleIDList } from "./particleidlist.js"; import { recoParticleID, preFilterRecoParticleID } from "./recoparticleid.js"; +import { spanWithColor } from "../lib/html-string.js"; export const views = { "Monte Carlo Particle Tree": { @@ -25,83 +26,91 @@ export const views = { viewFunction: mcParticleTree, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCTree, - description: - "

Red relations mean parent relation (from bottom to top), green relations mean daughter relation (from top to bottom).

", + description: `

${spanWithColor( + "Red", + "#AA0000" + )} relations mean parent relation (from bottom to top), ${spanWithColor( + "green", + "#00AA00" + )} relations mean daughter relation (from top to bottom).

`, }, "Reconstructed Particle Tree": { filters: setupNoFilter, viewFunction: recoParticleTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterRecoTree, - description: - "

A tree of the Reconstructed Particles. Purple relations mean relation between particles.

", + description: `

A tree of the Reconstructed Particles. ${spanWithColor( + "Purple", + "#AA00AA" + )} relations mean relation between particles.

`, }, "Track Tree": { filters: setupNoFilter, viewFunction: trackTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterTrackTree, - description: "

A tree of the Tracks.

", + description: `

A tree of the Tracks.

`, }, "Cluster Tree": { filters: setupNoFilter, viewFunction: clusterTree, scrollFunction: scrollTopLeft, preFilterFunction: preFilterClusterTree, - description: "

A tree of the Clusters.

", + description: `

A tree of the Clusters.

`, }, "RecoParticle-Cluster-Track-Vertex": { filters: setupNoFilter, viewFunction: recoClusterTrackVertex, scrollFunction: scrollTopCenter, preFilterFunction: preFilterRecoClusterTrackVertex, - description: - "

Relations that a Reconstruced Particle has with other objects. Green connections are towards Tracks, and sky blue connections are towards Clusters.

", + description: `

Relations that a Reconstruced Particle has with other objects. ${spanWithColor( + "Green", + "#AAAA00" + )} connections are towards Tracks, and ${spanWithColor( + "sky blue", + "#00AAAA" + )} connections are towards Clusters.

`, }, "Monte Carlo-Reconstructed Particle": { filters: setupNoFilter, viewFunction: mcRecoAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCReco, - description: - "

Association between Monte Carlo Particles and Reconstructed Particles. 1:1 relation.

", + description: `

Association between Monte Carlo Particles and Reconstructed Particles. 1:1 relation.

`, }, "Monte Carlo Particle-Track": { filters: setupNoFilter, viewFunction: mcTrackAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCTrack, - description: - "

Association between Monte Carlo Particles and Tracks. 1:1 relation.

", + description: `

Association between Monte Carlo Particles and Tracks. 1:1 relation.

`, }, "Monte Carlo Particle-Cluster": { filters: setupNoFilter, viewFunction: mcClusterAssociation, scrollFunction: scrollTopCenter, preFilterFunction: preFilterMCCluster, - description: - "

Association between Monte Carlo Particles and Clusters. 1:1 relation.

", + description: `

Association between Monte Carlo Particles and Clusters. 1:1 relation.

`, }, "ParticleID List": { filters: setupNoFilter, viewFunction: particleIDList, scrollFunction: scrollTopLeft, preFilterFunction: preFilterParticleIDList, - description: "

A list of ParticleIDs found in the event.

", + description: `

A list of ParticleIDs found in the event.

`, }, "Vertex List": { filters: setupNoFilter, viewFunction: vertexList, scrollFunction: scrollTopLeft, preFilterFunction: preFilterVertexList, - description: "

A list of Vertices found in the event.

", + description: `

A list of Vertices found in the event.

`, }, "ParticleID-Reconstructed Particle": { filters: setupNoFilter, viewFunction: recoParticleID, scrollFunction: scrollTopCenter, preFilterFunction: preFilterRecoParticleID, - description: - "

1:1 relation from ParticleID to Reconstructed Particle.

", + description: `

1:1 relation from ParticleID to Reconstructed Particle.

`, }, }; From 598f8ceec4203dc07a084ae2110b5b20d87f2f95 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 16 Jul 2024 15:41:52 -0500 Subject: [PATCH 57/60] remove commas to save space --- js/types/objects.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/types/objects.js b/js/types/objects.js index 4f9c90ff..6370034d 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -119,7 +119,7 @@ export class MCParticle extends EDMObject { ); const simulatorStatusFirstLetter = simulatorStatus .map((s) => s[0]) - .join(", "); + .join(""); const simulatorStatusString = simulatorStatusFirstLetter !== "" ? simulatorStatusFirstLetter From 136f20adf45968c714ce62297387cf0da1b16ea6 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Wed, 17 Jul 2024 09:46:23 -0500 Subject: [PATCH 58/60] add more vertical space for collection type title --- js/types/objects.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/js/types/objects.js b/js/types/objects.js index 483cac06..e52006d9 100644 --- a/js/types/objects.js +++ b/js/types/objects.js @@ -8,7 +8,7 @@ import { getName } from "../lib/getName.js"; import { linkTypes } from "./links.js"; import { parseCharge } from "../lib/parseCharge.js"; -const TOP_MARGIN = 40; +const TOP_MARGIN = 45; class EDMObject { constructor() { @@ -62,6 +62,7 @@ export class MCParticle extends EDMObject { this.texImg = null; this.color = "#dff6ff"; this.radius = 15; + this.height = 270; } updateTexImg(text) { @@ -85,7 +86,7 @@ export class MCParticle extends EDMObject { drawTex( ctx, boxCenterX, - this.y + this.height * 0.4, + this.y + TOP_MARGIN + 80, this.texImg, this.width ); @@ -94,7 +95,7 @@ export class MCParticle extends EDMObject { drawTex( ctx, boxCenterX, - this.y + this.height * 0.4, + this.y + TOP_MARGIN + 80, this.texImg, this.width ); @@ -107,7 +108,7 @@ export class MCParticle extends EDMObject { topLines.push("Gen. stat.: " + this.generatorStatus); topLines.push("Sim. stat.: " + this.simulatorStatus); - const bottomY = this.y + this.height * 0.6; + const bottomY = this.y + this.height * 0.65; const bottomLines = []; bottomLines.push("p = " + this.momentum + " GeV"); bottomLines.push("d = " + this.vertex + " mm"); From 6e03d82f56a26d3ed28c404e13b156d77fbf0bf4 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Wed, 17 Jul 2024 10:00:10 -0500 Subject: [PATCH 59/60] hide about eede button at beginning --- css/information.css | 4 ++++ js/main.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/css/information.css b/css/information.css index 951496b7..e6ebef1e 100644 --- a/css/information.css +++ b/css/information.css @@ -67,6 +67,10 @@ display: none; } +#information-button { + display: none; +} + #view-information-content { display: none; } diff --git a/js/main.js b/js/main.js index e581d726..5cf37d0c 100644 --- a/js/main.js +++ b/js/main.js @@ -40,8 +40,10 @@ function showEventSwitcher() { function showViewsMenu() { const viewsMenu = document.getElementById("views"); + const aboutButton = document.getElementById("information-button"); viewsMenu.style.display = "flex"; + aboutButton.style.display = "block"; } document.getElementById("input-file").addEventListener("change", (event) => { From 07291a8bdbee367f349ef1e7bd91b3833704582f Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Wed, 17 Jul 2024 10:04:01 -0500 Subject: [PATCH 60/60] set view name on info button --- js/views/views.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/js/views/views.js b/js/views/views.js index 2b61b1ac..982d1011 100644 --- a/js/views/views.js +++ b/js/views/views.js @@ -41,6 +41,11 @@ function scroll() { window.scrollTo(scrollLocations[index].x, scrollLocations[index].y); } +function setInfoButtonName(view) { + const button = document.getElementById("view-information-button"); + button.innerText = view; +} + const drawView = (view) => { paintButton(view); @@ -88,6 +93,7 @@ const drawView = (view) => { drawAll(viewCurrentObjects); getVisible(viewCurrentObjects, viewVisibleObjects); filters(viewObjects, viewCurrentObjects, viewVisibleObjects); + setInfoButtonName(getView()); canvas.onmousedown = (event) => { mouseDown(event, viewVisibleObjects, dragTools);