Using graphviz for layouting only

Hello, I’m currently implementing a graphical editor that displays elements connected through ports like shown on the following picture:

I’m currently looking for a way to allow the user to automatically layout the elements in the editor in a “nice” way (in the above picture, the elements were positioned manually).

I experimented a bit with graphviz and it seems that the layout algo would suit my needs. For instance, the following .dot input produces a nice layout for the graph:

[dot][code]graph {
rankdir=“LR”
node [shape=record];

goals [label="Goals | { <po1>output1 } | { <po2>output2 } | {  <po3>output3 }"];
fmu2 [label="FMU thermal4 | { <pi1>input1 | <po1>output1 } | { <pi2>input2 |  <po2>output2 } | { <pi3>input3 | <po3>output3 } | { <pi4>input4 | <po4>output4 }"]
fmu1 [label="FMU my_model | { <pi1>input1 | <po1>output1 } | { <pi2>input2 | }"];
chip [label="Main Chip | <pi1>Heat GenerationRate"]
pressure [label="Environment Pressure 1 | <pi1>Temperature | <pi2>Turbulence"]

goals:po1 -- fmu1:pi2
goals:po2 -- fmu1:pi1
goals:po3 -- fmu2:pi4
fmu1:po1 -- fmu2:pi2
fmu2:po2 -- chip:pi1
fmu2:po1 -- pressure:pi1
fmu2:po3 -- pressure:pi2

}[/code][/dot]

So, with this in mind I would like to use graphviz as a library in order to create a graph and feed my data to it, call the layout algorithm and read back the element positions in order to rearrange the content of my own editor.
So far I managed to use graphviz as a library on Windows and create a node for each of my element (by calling API methods like agopen and agnode). What I’m currently stuck with is the following:

  • Specify ports on the node (like in the dot language above): for now, I simply have a node per element but did not find any way to add ports to the nodes.
  • Read back the layout information once the layout algorithm has been invoked. I have no idea where I can find that information back. The Agnode_t struct does not contain any of that information it seems.

EDIT: sorry, I tried to edit the DOT section but can’t make it work, here is how it looks like:

Thanks!

You could also call dot -Tdot … or dot -Txdot … and parse the output of that.

Thanks for your answer. However like I said I would rather prefer to use graphviz as a library for now. If I can’t make this work, then I’ll think about invoking dot externally (I already thought about that).

Anyway, I made a bit of progress. I was able to create the graph by going through the dot language instead:

    GVC_t* gvc = gvContext();
    Agraph_t* graph = agmemread(dotString.c_str());

    // Use the directed graph layout engine
    gvLayout(gvc, graph, "dot");

dotString is a character string containing the dot syntax which was generated programmatically. I validated the output through an external graphviz editor and it produces the expected graph.

I also found that the position information should be stored in an attribute of the nodes, so I’m now trying to call agget on the nodes:

    for (const GraphElementPtr& element : m_elements) {
        auto iter = elmToIdMap.find(element.get());
        if (iter != elmToIdMap.end()) {
            Agnode_t* node = agnode(graph, iter->second.data(), 0);
            char* pos = agget(node, "pos");
            // Do something with pos
        }
    }

the m_elements array is my own code containing the elements displayed in my editor (a one-to-one match with the nodes in the graphviz graph). elmToIdMap maps my elements to their corresponding graphviz node Id (which I generated myself). Unfortunately, agget always returns NULL and I have no idea why. I debugged the code and the nodes are I generated the dot file to check if the layout information is present and this is the case:

graph {
	graph [bb="0,0,521.86,144.8",
		rankdir=LR
	];
	node [label="\N",
		shape=record
	];
	sfmu_my_model	[height=1.0472,
		label="FMU my_model | { <pi0>lbl  |  <po0>lbl  } | { <pi1>lbl  |  }",
		pos="144.86,91.3",
		rects="90,103.7,199.73,128.5 90,78.9,144.78,103.7 144.78,78.9,199.56,103.7 90,54.1,149.78,78.9 149.78,54.1,199.28,78.9",
		width=1.524];
	sfmu_thermal4	[height=1.7361,
		label="FMU thermal4 | { <pi0>lbl  |  <po0>lbl  } | { <pi1>lbl  |  <po1>lbl  } | { <pi2>lbl  |  <po2>lbl  } | { <pi3>lbl  |  <po3>lbl  }",
		pos="285.53,82.3",
		rects="235.73,119.5,335.33,144.3 235.73,94.7,285.51,119.5 285.51,94.7,335.29,119.5 235.73,69.9,285.51,94.7 285.51,69.9,335.29,94.7 235.73,45.1,285.51,69.9 285.51,45.1,335.29,69.9 235.73,20.3,285.51,45.1 285.51,20.3,335.29,45.1",
		width=1.3834];
	sfmu_my_model:po0 -- sfmu_thermal4:pi1	[pos="199.73,91.3 216.22,91.3 219.24,82.3 235.73,82.3"];
	s88a4c397_eb2c_470f_bfc4_1abddb607b8c	[height=1.0472,
		label="Environment Pressure 1 | { <pi0>lbl  |  } | { <pi1>lbl  |  }",
		pos="446.6,106.3",
		rects="371.33,118.7,521.86,143.5 371.33,93.9,452.11,118.7 452.11,93.9,521.61,118.7 371.33,69.1,452.11,93.9 452.11,69.1,521.61,93.9",
		width=2.0907];
	sfmu_thermal4:po0 -- s88a4c397_eb2c_470f_bfc4_1abddb607b8c:pi0	[pos="335.33,107.1 351.34,107.1 355.33,106.3 371.33,106.3"];
	sfmu_thermal4:po2 -- s88a4c397_eb2c_470f_bfc4_1abddb607b8c:pi1	[pos="335.33,57.5 354.56,57.5 352.1,81.5 371.33,81.5"];
	s39223c79_e62d_4d1e_9350_0c6f67f72b64	[height=0.70278,
		label="Main Chip | { <pi0>lbl  |  }",
		pos="446.6,25.3",
		rects="408.46,25.3,484.74,50.1 408.46,0.5,452.24,25.3 452.24,0.5,484.74,25.3",
		width=1.0594];
	sfmu_thermal4:po1 -- s39223c79_e62d_4d1e_9350_0c6f67f72b64:pi0	[pos="335.33,82.3 379.82,82.3 362.97,12.9 407.46,12.9"];
	sGoals	[height=1.3917,
		label="Goals | { |  <po0>lbl  } | { |  <po1>lbl  } | { |  <po2>lbl  }",
		pos="27,62.3",
		rects="0,87.1,54,111.9 0,62.3,20.5,87.1 20.5,62.3,53.279,87.1 0,37.5,20.5,62.3 20.5,37.5,53.279,62.3 0,12.7,20.5,37.5 20.5,12.7,53.279,37.5",
		width=0.75];
	sGoals:po0 -- sfmu_my_model:pi1	[pos="54,74.7 70.41,74.7 73.59,66.5 90,66.5"];
	sGoals:po1 -- sfmu_my_model:pi0	[pos="54,49.9 78.384,49.9 65.616,91.3 90,91.3"];
	sGoals:po2 -- sfmu_thermal4:pi3	[pos="54,25.1 134.84,25.1 154.89,32.7 235.73,32.7"];
}

Is there something wrong with the way I’m calling agget? I passed 0 for the createflag argument and I validated that the function returns NULL in case I’m passing an ID not present in the graph. So, the nodes returned by the function are the ones already present in the graph.

Yes, calling the library API is certainly cleaner.

Sorry, nothing jumps out at me as obviously incorrect in your code but I’m not an expert in this area.