diff --git a/js/draw/box.js b/js/draw/box.js index ae46d8d..01929b7 100644 --- a/js/draw/box.js +++ b/js/draw/box.js @@ -92,29 +92,34 @@ export function buildBox(object) { } export function addHoverModal(box, lines) { - let objectModal = null; + const objectModal = createObjectModal(lines, box.width); + const objectModalWidth = parseInt(objectModal.width); + const boxWidth = parseInt(box.width); + + let showModal = false; + + const clean = () => { + showModal = false; + removeObjectModal(objectModal); + }; box.on("pointerover", () => { - objectModal = createObjectModal(lines, box.width); - const objectModalWidth = parseInt(objectModal.width); - const boxWidth = parseInt(box.width); - const x = parseInt(box.position.x); - const xPosition = (boxWidth - objectModalWidth) / 2 + x; - const y = box.position.y; - - const timeout = setTimeout(() => { + if (showModal) { + return; + } + showModal = true; + setTimeout(() => { + if (!showModal) { + return; + } + const x = parseInt(box.position.x); + const xPosition = (boxWidth - objectModalWidth) / 2 + x; + const y = box.position.y; renderObjectModal(objectModal, xPosition, y); }, 500); - - const clean = () => { - clearTimeout(timeout); - removeObjectModal(objectModal); - objectModal = null; - }; - - box.on("pointerdown", clean); - box.on("pointerout", clean); }); + box.on("pointerdown", clean); + box.on("pointerout", clean); } export function addTitleToBox(title, box) { diff --git a/js/filters/pre-filter.js b/js/filters/pre-filter.js index bad016e..2fe8e30 100644 --- a/js/filters/pre-filter.js +++ b/js/filters/pre-filter.js @@ -11,14 +11,30 @@ export function preFilterAssociation( const association = currentObjects.associations[associationName]; - const fromCollection = association.map((association) => association.from); + const added = new Set(); + const fromCollection = []; + const toCollection = []; - const toCollection = association.map((association) => association.to); + association.forEach((relation) => { + const from = relation.from; + const fromId = `${from.index}-${from.collectionId}`; - viewObjects.datatypes[fromCollectionName].collection = fromCollection; + if (!added.has(fromId)) { + added.add(fromId); + fromCollection.push(from); + } - viewObjects.datatypes[toCollectionName].collection = toCollection; + const to = relation.to; + const toId = `${to.index}-${to.collectionId}`; + if (!added.has(toId)) { + added.add(toId); + toCollection.push(to); + } + }); + + viewObjects.datatypes[fromCollectionName].collection = fromCollection; + viewObjects.datatypes[toCollectionName].collection = toCollection; viewObjects.associations[associationName] = association; } @@ -58,7 +74,18 @@ export function preFilterOneWay( currentObjects.datatypes[fromCollectionName].oneToOne[relationName]; const fromCollection = relations.map((relation) => relation.from); - const toCollection = relations.map((relation) => relation.to); + + const added = new Set(); + const toCollection = []; + relations.forEach((relation) => { + const to = relation.to; + const toId = `${to.index}-${to.collectionId}`; + + if (!added.has(toId)) { + added.add(toId); + toCollection.push(to); + } + }); viewObjects.datatypes[fromCollectionName].oneToOne[relationName] = relations; viewObjects.datatypes[fromCollectionName].collection = fromCollection; diff --git a/js/views/templates/list.js b/js/views/templates/list.js index caef9ab..971c001 100644 --- a/js/views/templates/list.js +++ b/js/views/templates/list.js @@ -1,27 +1,69 @@ +const minHorizontalGapPercentage = 0.3; +const verticalGapPercentage = 0.3; + +const bestHorizontalFit = (windowWidth, objectWidth) => { + let columns = 1; + let percentage = + (windowWidth - columns * objectWidth) / (objectWidth * (1 + columns)); + let prevPercentage = percentage; + + while (percentage >= minHorizontalGapPercentage) { + prevPercentage = percentage; + columns += 1; + percentage = + (windowWidth - columns * objectWidth) / (objectWidth * (1 + columns)); + } + + return [columns - 1, prevPercentage]; +}; + export function listView(collection) { const width = window.innerWidth; + const length = collection.length; - const gap = 1; - const objWidth = collection[0].width; - const objHorizontalGap = gap * objWidth; const objHeight = collection[0].height; - const objVerticalGap = gap * objHeight; + const objVerticalGap = parseInt(verticalGapPercentage * objHeight); + const objWidth = collection[0].width; + const [cols, horizontalGapPercentage] = bestHorizontalFit(width, objWidth); + const objHorizontalGap = parseInt(horizontalGapPercentage * objWidth); - const cols = Math.ceil(width / (objWidth + objHorizontalGap)); - const rows = Math.ceil(collection.length / cols); + const rows = Math.ceil(length / cols); - const height = rows * (objHeight + objVerticalGap / 2) + objVerticalGap / 2; + const height = rows * (objHeight + objVerticalGap) + objVerticalGap; const finalHeight = 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; + const allX = []; + for (let i = 1; i <= cols; i++) { + allX.push(i * objHorizontalGap + (i - 1) * objWidth); } + const maxLength = rows * cols; + const halfCols = Math.ceil(cols / 2); + + collection.forEach((object, index) => { + const numElement = index + 1; + + const objRow = Math.ceil(numElement / cols); + let objCol; + const res = numElement % cols; + if (res === 0) { + objCol = cols; + } else { + objCol = res; + } + + const rowLength = + maxLength - numElement >= cols ? cols : length - cols * (rows - 1); + const halfCol = Math.ceil(rowLength / 2); + + const allXIndex = halfCols - (halfCol - objCol); + + const x = allX[allXIndex - 1]; + const y = objRow * objVerticalGap + (objRow - 1) * objHeight; + + object.x = x; + object.y = y; + }); + return [width, finalHeight]; } diff --git a/js/views/templates/recoclustertrack.js b/js/views/templates/recoclustertrack.js index f553380..d278e40 100644 --- a/js/views/templates/recoclustertrack.js +++ b/js/views/templates/recoclustertrack.js @@ -135,17 +135,24 @@ export function preFilterRecoClusterTrackVertex(currentObjects, viewObjects) { const fromCollection = fromDatatype.collection; + const added = new Set(); + const recoParticles = []; const clusters = []; const tracks = []; const vertexCollection = []; fromCollection.forEach((particle) => { + const id = `${particle.index}-${particle.collectionId}`; + const clusterRelations = particle.oneToManyRelations["clusters"]; const trackRelations = particle.oneToManyRelations["tracks"]; const vertexRelation = particle.oneToOneRelations["startVertex"]; - const total = clusterRelations.length + trackRelations.length; + const total = + clusterRelations.length + + trackRelations.length + + (vertexRelation !== undefined ? 1 : 0); if (total === 0) { return; @@ -153,20 +160,38 @@ export function preFilterRecoClusterTrackVertex(currentObjects, viewObjects) { clusterRelations.forEach((clusterRelation) => { const cluster = clusterRelation.to; - clusters.push(cluster); + const clusterId = `${cluster.index}-${cluster.collectionId}`; + + if (!added.has(clusterId)) { + added.add(clusterId); + clusters.push(cluster); + } }); trackRelations.forEach((trackRelation) => { const track = trackRelation.to; - tracks.push(track); + const trackId = `${track.index}-${track.collectionId}`; + + if (!added.has(trackId)) { + added.add(trackId); + tracks.push(track); + } }); if (vertexRelation !== undefined) { const vertex = vertexRelation.to; - vertexCollection.push(vertex); + const vertexId = `${vertex.index}-${vertex.collectionId}`; + + if (!added.has(vertexId)) { + added.add(vertexId); + vertexCollection.push(vertex); + } } - recoParticles.push(particle); + if (!added.has(id)) { + added.add(id); + recoParticles.push(particle); + } }); viewObjects.datatypes["edm4hep::ReconstructedParticle"].collection = diff --git a/js/views/templates/tree.js b/js/views/templates/tree.js index 40a57d7..16275be 100644 --- a/js/views/templates/tree.js +++ b/js/views/templates/tree.js @@ -1,28 +1,68 @@ +import { listView } from "./list.js"; + // All particles that are related to itself have an one to many relation export function buildTree(collection, relationOfReference) { + collection.forEach((object) => { + object.row = null; + }); + const nodes = new Set(); const children = new Set(); + const childless = new Set(); for (const object of collection) { - const objects = object.oneToManyRelations[relationOfReference].map( + const childObjects = object.oneToManyRelations[relationOfReference].map( (link) => link.to ); - nodes.add(`${object.index}-${object.collectionId}`); - for (const childObject of objects) { + const objectId = `${object.index}-${object.collectionId}`; + nodes.add(objectId); + + if (childObjects.length === 0) { + childless.add(objectId); + } + + for (const childObject of childObjects) { children.add(`${childObject.index}-${childObject.collectionId}`); } } const rootNodesIds = nodes.difference(children); - const rootNodes = []; + const childlessRootNodesIds = rootNodesIds.intersection(childless); + const rootNodesWithChildrenIds = rootNodesIds.difference( + childlessRootNodesIds + ); + const rootNodesWithChildren = []; + const childlessRootNodes = []; collection.forEach((object) => { - if (rootNodesIds.has(`${object.index}-${object.collectionId}`)) { - rootNodes.push(object); + const objectId = `${object.index}-${object.collectionId}`; + if (rootNodesWithChildrenIds.has(objectId)) { + rootNodesWithChildren.push(object); + } + if (childlessRootNodesIds.has(objectId)) { + childlessRootNodes.push(object); } }); - rootNodes.forEach((rootNode) => { + 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 [_, listHeight] = listView(childlessRootNodes); + + let cols = (window.innerWidth - horizontalGap) / (boxWidth + horizontalGap); + const decimal = cols % 1; + + if (decimal >= 0.5) { + cols = cols + 1; + } + cols = Math.floor(cols); + + const rows = Math.ceil(childlessRootNodes.length / cols); + const startingRow = rows; + + rootNodesWithChildren.forEach((rootNode) => { const stack = [[rootNode, 0]]; while (stack.length > 0) { @@ -41,15 +81,15 @@ export function buildTree(collection, relationOfReference) { } }); - 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 (row === null) { + return; + } + if (matrix[row] === undefined) { matrix[row] = []; } @@ -58,21 +98,29 @@ export function buildTree(collection, relationOfReference) { matrix.forEach((row, i) => { row.forEach((object, j) => { - object.x = j * horizontalGap + j * boxWidth + horizontalGap; - object.y = i * verticalGap + i * boxHeight + verticalGap; + const row = i + startingRow; + const col = j; + object.x = col * horizontalGap + col * boxWidth + horizontalGap; + object.y = row * verticalGap + row * boxHeight + verticalGap; }); }); - const totalWidth = - boxWidth * matrix[0].length + horizontalGap * (matrix[0].length + 1); + let maxWidth = 0; + matrix.forEach((row) => { + const rowLength = row.length; + if (rowLength > maxWidth) { + maxWidth = rowLength; + } + }); + + const totalWidth = boxWidth * maxWidth + horizontalGap * (maxWidth + 1); const width = totalWidth > window.innerWidth ? totalWidth : window.innerWidth; const totalHeight = boxHeight * (matrix.length + 1) + verticalGap * (matrix.length + 2); - const height = totalHeight > window.innerHeight ? totalHeight : window.innerHeight; - return [width, height]; + return [width, height + listHeight]; }