Using graphviz API directly to render graphs

Hi.
I’m trying to use the gvrender_engine_t API to get the layout information performed using the dot layout algorithm. I have followed the official guide to write my own rendering plugin. Unfortunately I only noticed the deployment approach (i.e. compiling as a shared object, copying to a specific folder, … as outlined in Chapter 6) at the end. I cloned the repo to see if there is a way to “hack in” my gvplugin_library_t at runtime and noticed the gvAddLibrary function. I tried the following:

gvrender_engine_t engine {
    .begin_job = &my_begin_job,
    .end_job = &my_end_job,
    ...
};
gvrender_features_t features {
    .flags = ...
    ...
};
gvplugin_installed_t render_plugins[] = {
    {0,    // idx
     "qt", // name
     9001, // "quality"
     &engine,
     &features},
    {0, nullptr, 0, nullptr, nullptr} // sentinel
};
gvplugin_api_t apis[] = {
    {API_render, render_plugins}, {static_cast<api_t>(0), nullptr} // sentinel
};
gvplugin_library_t gvplugin_qt_LTX_library{const_cast<char*>("myplug"), apis};

I can verify that my plugin is loaded:

GVC_t* gvc;
gvc = gvContext();
int size = 0;
char** plugin_before = gvPluginList(gvc, "render", &size);
spdlog::info("plugins before: {} (size: {})",
                fmt::join(plugin_before, plugin_before + size, ", "),
                size);
gvAddLibrary(gvc, &gvplugin_qt_LTX_library);
size = 0;
char** plugin_after = gvPluginList(gvc, "render", &size);
spdlog::info("plugins after: {} (size: {})", fmt::join(plugin_after, plugin_after + size, ", "), size);

which shows “qt” as a valid rendering plugin, however, I cannot use it with gvRender and it also seems to interfere with the render call directly after it:

auto* cgraph = createMyGraph();
gvLayout(gvc, cgraph, "dot");
gvRender(gvc, cgraph, "qt", nullptr); // Error: Error: Format: "qt" not recognized. Use one of: canon cmap cmapx cmapx_np dot dot_json ...
gvRenderFilename(gvc, cgraph, "png", "/tmp/out.png"); // Error: renderer for qt is unavailable
gvRenderFilename(gvc, cgraph, "png", "/tmp/out.png"); // 2nd call works!

I’m gonna look deeper into the code to figure out where the error is coming from, but I would appreciate any tips regarding this approach. Knowing that it’s a dead-end would e.g. help me go for the alternative, which is reading out the predefined attributes attached to my cgraph, as mentioned in the docs (Chapter 2.3).

I had the same issue and found this post.
I looked at the sources and looks like I figured this out.

Unexpectedly for me, gvRenderContext in my case and, I assume, gvRender* treat format not as render or layout, but as device. So to call gvRender* with custom implemetation one has to create and register a device plugin (API_device). Let’s call it qtdevice.

Then, if format specifies device, how do you specify particular render?
I haven’t read deep into the code, but it looks like Graphviz utilizes dependencies. To make sure the device uses the render, the device’s name must be: <arbitrary device name>:<render name>.
I.e., if custom render plugin is provided and registered with name qtrender, when to bind device to it the device plugin must have name <arbitrary device name>:qtrender; in our example qtdevice:qtrender.

It looks like the format of gvRender* also supports dependecies constraints. If format is set, in our example, to qtdevice:qtrender, implementation makes sure to load and use the exact same chain.

Overall:

  • provide both device and render plugins. Make sure device plugin requires provided render - its name must be of format <arbitrary device plugin name>:<required render plugin name>. (Please note that in countrary to documentation, device plugin must have features filled in.)
// language: C++
char Model_Plugin_PackageName[] = "qt\0";
char Model_Plugin_DeviceName[] = "qtdevice:qtrender\0";
char Model_Plugin_RenderName[] = "qtrender\0";
gvplugin_library_t gvplugin_qt_LTX_library =
	{
		.packagename = Model_Plugin_PackageName,
		.apis =
		(gvplugin_api_t[3]){
			{
				.api = API_device,
				.types = (gvplugin_installed_t[2]){
					{
						.id = 0,
						.type = Model_Plugin_DeviceName,
						.quality = std::numeric_limits<int>::max(),
						.engine = &Model_Plugin_DeviceEngine,
						.features = &Model_Plugin_DeviceFeatures
					},
					{
						0,
						nullptr,
						0,
						nullptr,
						nullptr
					}
				}
			},
			{
				.api = API_render,
				.types = (gvplugin_installed_t[2]){
					{
						.id = 0,
						.type = Model_Plugin_RenderName,
						.quality = std::numeric_limits<int>::max(),
						.engine = &Model_Plugin_RenderEngine,
						.features = &Model_Plugin_RenderFeatures
					},
					{
						0,
						nullptr,
						0,
						nullptr,
						nullptr
					}
				}
			},
				{
				.api = (api_t)0,
				.types = nullptr
			}
		}
	};
  • register plugins/package with gvAddLibrary.
// language C++
gvAddLibrary(graphVizContext, &gvplugin_qt_LTX_library);
  • use gvRender* specifying format as <device plugin name>:<render plugin name>.
// language C++
gvLayout(graphVizContext, currentGraph, "fdp");
gvRenderContext(graphVizContext, currentGraph, "qtdevice:qtrender", nullptr);
gvFreeLayout(graphVizContext, currentGraph);