Files
don_confiao_frontend/src/services/graph/graph-layout.js

77 lines
2.1 KiB
JavaScript

/**
* Servicio genérico de grafos en capas (columnas).
*
* Provee un layout determinista por columnas (un tipo por columna) y un
* adaptador al formato `elements` de cytoscape.js. No conoce el dominio
* (provenance, organigrama, etc.), solo el modelo de grafo:
* node: { id, kind, label, image?, entity? }
* edge: { from, to } (ids de los nodos)
*/
const DEFAULTS = {
columnWidth: 220,
rowHeight: 64,
padding: 24,
}
function groupByColumn (nodes, columnOf) {
const columns = {}
for (const node of nodes) {
const column = columnOf(node) ?? 0
if (!columns[column]) columns[column] = []
columns[column].push(node)
}
return columns
}
export function computeColumnLayout (nodes, edges, options = {}) {
const { columnOf, columnWidth = DEFAULTS.columnWidth, rowHeight = DEFAULTS.rowHeight, padding = DEFAULTS.padding } = options
const columns = groupByColumn(nodes, columnOf)
const columnKeys = Object.keys(columns).map(Number)
const maxColumn = columnKeys.length > 0 ? Math.max(...columnKeys) : 0
const maxRows = columnKeys.length > 0 ? Math.max(...columnKeys.map(c => columns[c].length)) : 0
const positionedNodes = nodes.map(node => {
const column = columnOf(node) ?? 0
const index = columns[column].indexOf(node)
return {
...node,
x: padding + column * columnWidth + columnWidth / 2,
y: padding + index * rowHeight + rowHeight / 2,
}
})
return {
nodes: positionedNodes,
edges,
width: padding * 2 + (maxColumn + 1) * columnWidth,
height: padding * 2 + maxRows * rowHeight,
}
}
export function toCytoscapeElements ({ nodes, edges }) {
return [
...nodes.map(node => {
const classes = node.image ? [node.kind, 'has-image'] : [node.kind]
return {
data: {
id: node.id,
kind: node.kind,
label: node.label,
image: node.image || null,
entity: node.entity || null,
},
classes,
position: { x: node.x, y: node.y },
}
}),
...edges.map(edge => ({
data: {
id: `edge:${edge.from}:${edge.to}`,
source: edge.from,
target: edge.to,
},
})),
]
}