mirror of
https://github.com/grafana/grafana.git
synced 2024-12-27 09:21:35 -06:00
Nodegraph: Fix issue with rendering single node (#84930)
Fix for single node graph case
This commit is contained in:
parent
dd93d9958d
commit
aba65747c9
@ -18,6 +18,7 @@ const esModules = [
|
||||
'@kusto/monaco-kusto',
|
||||
'monaco-editor',
|
||||
'lodash-es',
|
||||
'@msagl',
|
||||
].join('|');
|
||||
|
||||
module.exports = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CorsWorker as Worker } from 'app/core/utils/CorsWorker';
|
||||
|
||||
export const createWorker = () => new Worker(new URL('./layout.worker.js', import.meta.url));
|
||||
export const createMsaglWorker = () => new Worker(new URL('./layoutMsagl.worker.js', import.meta.url));
|
||||
export const createMsaglWorker = () => new Worker(new URL('./layoutLayered.worker.js', import.meta.url));
|
||||
|
@ -10,12 +10,6 @@ import {
|
||||
} from '@msagl/core';
|
||||
import { parseDot } from '@msagl/parser';
|
||||
|
||||
addEventListener('message', async (event) => {
|
||||
const { nodes, edges, config } = event.data;
|
||||
const [newNodes, newEdges] = layout(nodes, edges, config);
|
||||
postMessage({ nodes: newNodes, edges: newEdges });
|
||||
});
|
||||
|
||||
/**
|
||||
* Use d3 force layout to lay the nodes in a sensible way. This function modifies the nodes adding the x,y positions
|
||||
* and also fills in node references in edges instead of node ids.
|
||||
@ -23,7 +17,7 @@ addEventListener('message', async (event) => {
|
||||
export function layout(nodes, edges) {
|
||||
const { mappedEdges, DOTToIdMap } = createMappings(nodes, edges);
|
||||
|
||||
const dot = edgesToDOT(mappedEdges);
|
||||
const dot = graphToDOT(mappedEdges, DOTToIdMap);
|
||||
const graph = parseDot(dot);
|
||||
const geomGraph = new GeomGraph(graph);
|
||||
for (const e of graph.deepEdges) {
|
||||
@ -142,58 +136,41 @@ function createMappings(nodes, edges) {
|
||||
// Key is an iteration index and value is actual ID of the node
|
||||
const DOTToIdMap = {};
|
||||
|
||||
// Crate the maps both ways
|
||||
let index = 0;
|
||||
for (const edge of edges) {
|
||||
if (!idToDOTMap[edge.source]) {
|
||||
idToDOTMap[edge.source] = index.toString(10);
|
||||
DOTToIdMap[index.toString(10)] = edge.source;
|
||||
index++;
|
||||
}
|
||||
for (const node of nodes) {
|
||||
idToDOTMap[node.id] = index.toString(10);
|
||||
DOTToIdMap[index.toString(10)] = node.id;
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!idToDOTMap[edge.target]) {
|
||||
idToDOTMap[edge.target] = index.toString(10);
|
||||
DOTToIdMap[index.toString(10)] = edge.target;
|
||||
index++;
|
||||
}
|
||||
for (const edge of edges) {
|
||||
mappedEdges.push({ source: idToDOTMap[edge.source], target: idToDOTMap[edge.target] });
|
||||
}
|
||||
|
||||
return {
|
||||
mappedEdges,
|
||||
DOTToIdMap,
|
||||
idToDOTMap,
|
||||
};
|
||||
}
|
||||
|
||||
function toDOT(edges, graphAttr = '', edgeAttr = '') {
|
||||
function graphToDOT(edges, nodeIDsMap) {
|
||||
let dot = `
|
||||
digraph G {
|
||||
${graphAttr}
|
||||
rankdir="LR"; TBbalance="min"
|
||||
`;
|
||||
for (const edge of edges) {
|
||||
dot += edge.source + '->' + edge.target + ' ' + edgeAttr + '\n';
|
||||
dot += edge.source + '->' + edge.target + ' ' + '[ minlen=3 ]\n';
|
||||
}
|
||||
dot += nodesDOT(edges);
|
||||
dot += nodesDOT(nodeIDsMap);
|
||||
dot += '}';
|
||||
return dot;
|
||||
}
|
||||
|
||||
function edgesToDOT(edges) {
|
||||
return toDOT(edges, 'rankdir="LR"; TBbalance="min"', '[ minlen=3 ]');
|
||||
}
|
||||
|
||||
function nodesDOT(edges) {
|
||||
function nodesDOT(nodeIdsMap) {
|
||||
let dot = '';
|
||||
const visitedNodes = new Set();
|
||||
// TODO: height/width for default sizing but nodes can have variable size now
|
||||
const attr = '[fixedsize=true, width=1.2, height=1.7] \n';
|
||||
for (const edge of edges) {
|
||||
if (!visitedNodes.has(edge.source)) {
|
||||
dot += edge.source + attr;
|
||||
}
|
||||
if (!visitedNodes.has(edge.target)) {
|
||||
dot += edge.target + attr;
|
||||
}
|
||||
for (const node of Object.keys(nodeIdsMap)) {
|
||||
dot += node + ' [fixedsize=true, width=1.2, height=1.7] \n';
|
||||
}
|
||||
return dot;
|
||||
}
|
10
public/app/plugins/panel/nodeGraph/layeredLayout.test.ts
Normal file
10
public/app/plugins/panel/nodeGraph/layeredLayout.test.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { layout } from './layeredLayout';
|
||||
|
||||
describe('layout', () => {
|
||||
it('can render single node', () => {
|
||||
const nodes = [{ id: 'A', incoming: 0 }];
|
||||
const edges: unknown[] = [];
|
||||
const graph = layout(nodes, edges);
|
||||
expect(graph).toEqual([[{ id: 'A', incoming: 0, x: 0, y: 0 }], []]);
|
||||
});
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
import { layout } from './layout.worker.utils';
|
||||
import { layout } from './forceLayout';
|
||||
|
||||
// Separate from main implementation so it does not trip out tests
|
||||
addEventListener('message', (event) => {
|
||||
const { nodes, edges, config } = event.data;
|
||||
layout(nodes, edges, config);
|
||||
|
@ -0,0 +1,8 @@
|
||||
import { layout } from './layeredLayout';
|
||||
|
||||
// Separate from main implementation so it does not trip out tests
|
||||
addEventListener('message', async (event) => {
|
||||
const { nodes, edges, config } = event.data;
|
||||
const [newNodes, newEdges] = layout(nodes, edges, config);
|
||||
postMessage({ nodes: newNodes, edges: newEdges });
|
||||
});
|
@ -1,7 +1,7 @@
|
||||
import { Config } from 'app/plugins/panel/nodeGraph/layout';
|
||||
import { EdgeDatum, NodeDatum } from 'app/plugins/panel/nodeGraph/types';
|
||||
|
||||
const { layout } = jest.requireActual('../../app/plugins/panel/nodeGraph/layout.worker.utils.js');
|
||||
const { layout } = jest.requireActual('../../app/plugins/panel/nodeGraph/forceLayout.js');
|
||||
|
||||
class LayoutMockWorker {
|
||||
timeout: number | undefined;
|
||||
|
Loading…
Reference in New Issue
Block a user