77 lines
2.1 KiB
JavaScript
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,
|
|
},
|
|
})),
|
|
]
|
|
}
|