From 24cfcbe7a6eb79ae0019a7f481fe627e509582f9 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 10 Aug 2024 15:12:01 -0500 Subject: [PATCH 1/5] remove duplicate objects to fix #74 --- js/filters/pre-filter.js | 37 ++++++++++++++++++++++---- js/views/templates/recoclustertrack.js | 35 ++++++++++++++++++++---- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/js/filters/pre-filter.js b/js/filters/pre-filter.js index bad016ed..2fe8e30a 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/recoclustertrack.js b/js/views/templates/recoclustertrack.js index f5533808..d278e405 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 = From df44b9b35793762418005be06cb6a1f3a1aca2e6 Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 10 Aug 2024 16:02:20 -0500 Subject: [PATCH 2/5] fix hover, modal dissapears and won't stay as expected --- js/draw/box.js | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/js/draw/box.js b/js/draw/box.js index ae46d8d6..01929b79 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) { From cbbfc6c8025c22b9dcf866a6e9adee05f0a3632a Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Sat, 10 Aug 2024 19:05:06 -0500 Subject: [PATCH 3/5] incorporate suggested changes for tree views and list views --- js/views/templates/list.js | 61 ++++++++++++++++++++------- js/views/templates/tree.js | 84 ++++++++++++++++++++++++++++++-------- 2 files changed, 113 insertions(+), 32 deletions(-) diff --git a/js/views/templates/list.js b/js/views/templates/list.js index caef9ab2..01d741cd 100644 --- a/js/views/templates/list.js +++ b/js/views/templates/list.js @@ -1,27 +1,60 @@ +const horizontalGapPercentage = 0.4; +const verticalGapPercentage = 0.3; + 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 objHorizontalGap = parseInt(horizontalGapPercentage * objWidth); const objHeight = collection[0].height; - const objVerticalGap = gap * objHeight; + const objVerticalGap = parseInt(verticalGapPercentage * objHeight); - const cols = Math.ceil(width / (objWidth + objHorizontalGap)); - const rows = Math.ceil(collection.length / cols); + let cols = (width - objHorizontalGap) / (objWidth + objHorizontalGap); + const decimal = cols % 1; - const height = rows * (objHeight + objVerticalGap / 2) + objVerticalGap / 2; - const finalHeight = height > window.innerHeight ? height : window.innerHeight; + if (decimal >= 0.5) { + cols = cols + 1; + } + cols = Math.floor(cols); - 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; + const rows = Math.ceil(length / cols); - collection[i].x = x; - collection[i].y = y; + const height = rows * (objHeight + objVerticalGap) + objVerticalGap; + const finalHeight = height > window.innerHeight ? height : window.innerHeight; + + 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/tree.js b/js/views/templates/tree.js index 40a57d75..16275be2 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]; } From 432afd6b0cb5382793a2e28d21eea597b7ee77dd Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Mon, 12 Aug 2024 16:58:42 -0500 Subject: [PATCH 4/5] at least show object without right gap --- js/views/templates/list.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/js/views/templates/list.js b/js/views/templates/list.js index 01d741cd..2066095a 100644 --- a/js/views/templates/list.js +++ b/js/views/templates/list.js @@ -13,7 +13,10 @@ export function listView(collection) { let cols = (width - objHorizontalGap) / (objWidth + objHorizontalGap); const decimal = cols % 1; - if (decimal >= 0.5) { + const minDecimal = + (objWidth + 0.5 * objHorizontalGap) / (objWidth + objHorizontalGap); + + if (decimal >= minDecimal) { cols = cols + 1; } cols = Math.floor(cols); From f42bddc9289f5db30a63538dfe8704847dff12bf Mon Sep 17 00:00:00 2001 From: Braulio Rivas Abad Date: Tue, 13 Aug 2024 16:41:39 -0500 Subject: [PATCH 5/5] search for best number of columns to center horizontally --- js/views/templates/list.js | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/js/views/templates/list.js b/js/views/templates/list.js index 2066095a..971c001b 100644 --- a/js/views/templates/list.js +++ b/js/views/templates/list.js @@ -1,25 +1,31 @@ -const horizontalGapPercentage = 0.4; +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 objWidth = collection[0].width; - const objHorizontalGap = parseInt(horizontalGapPercentage * objWidth); const objHeight = collection[0].height; const objVerticalGap = parseInt(verticalGapPercentage * objHeight); - - let cols = (width - objHorizontalGap) / (objWidth + objHorizontalGap); - const decimal = cols % 1; - - const minDecimal = - (objWidth + 0.5 * objHorizontalGap) / (objWidth + objHorizontalGap); - - if (decimal >= minDecimal) { - cols = cols + 1; - } - cols = Math.floor(cols); + const objWidth = collection[0].width; + const [cols, horizontalGapPercentage] = bestHorizontalFit(width, objWidth); + const objHorizontalGap = parseInt(horizontalGapPercentage * objWidth); const rows = Math.ceil(length / cols);