问题背景
Graphviz是个好东西啊,但是可惜的是我并没有找到可以直接用C++画图的程序?(可能是我找的不对劲?
需求: 完成一个C++类,能够通过C++类中的函数调用等方法完成绘图工作。
前提: 已经完成Graphviz的安装,并且把对应的路径加入到系统的PATH中,即“前传”部分已经完成。
解决方案
- 完成一个C++画图类,类比python networkx那种,可以调用函数来画图。
- 创建一个临时文件,按照过程写入Graphviz的dot文件
- 能够区分有向图跟无向图
- 未完待续…
应用实例
//main.cpp
#include "DrawGraph.h"
int main() {
DrawGraph d;
d.Start();
d.Create_Node("TEST1");
d.Create_Node("TEST2");
d.Create_Relationship("TEST1", "TEST2", DrawGraph::Graph, "knows");
d.End();
d.Get_Png("H:/test.png");
return 0;
}
生成图片:
Future Work
- 增加节点的多种属性设置,基本按照Graphviz的Attribute来设定
- 增加子图模式,允许在整张图之中增加子图。
- 增加边的多种属性模式,包括形状颜色等。
- 提供面向对象的建模方式,方便快速排版生成的dot文件。
- 允许对其中的节点进行层级排布,允许加入顺序关系。
- 支持html等格式的初始化节点。
- 支持从邻接矩阵、邻接表中导入图形。
- 未完待续…
Code
//DrawGraph.h
#pragma once
#include <map>
#include <string>
#include <fstream>
class DrawGraph
{
private:
std::map<std::string, int> str2int;
std::map<int, std::string> int2str;
std::string temp_file = "./temp.tp";
int node_cnt = 0, rela_cnt = 0;
public:
static const int DiGraph = 1, Graph = 0;
void Start(int mode = 0);//开始画图的之前需要选定画图模式,DrawGraph::DiGraph or DrawGraph::Graph
int Create_Node(std::string node_name);//创建一个节点,并且节点名称为node_name
int Create_Node(int node_name);//创建一个节点,节点名为一个整数数字
int Find_Node(std::string node_name);//根据节点名称找到节点编号(方便上层开发的时候,不需要存下每个创建过的节点的具体名字,只需要存数字即可)
std::string Find_Node(int node_id);//通过节点的编号找到节点的名称
int Create_Relationship(std::string start_name, std::string end_name, int mode = 0, std::string label = "");//创建一个边,mode是有向图和无向图两种,label是边上的标签
int Create_Relationship(int start_id, int end_id, int mode = 0, std::string label = "");//根据节点编号创建边,仍然是方便上层开发
void End();//表示这个图画完了
void Print_Dot(std::string file_path);//把.dot文件生成到指定的目录下,方便使用和人工更改。file_path最好是以".dot"结尾。
void Get_Png(std::string file_path);//画图,file_path以png结尾,表示最后输出的图片目录
};
//DrawGraph.cpp
#include "DrawGraph.h"
void DrawGraph::Start(int mode)
{
FILE* fp = fopen(temp_file.c_str(), "w");
if (mode == DiGraph)
fprintf(fp, "digraph my_graph{\n");
else if (mode == Graph)
fprintf(fp, "graph my_graph{\n");
fclose(fp);
}
int DrawGraph::Create_Node(std::string node_name)
{
FILE* fp = fopen(temp_file.c_str(), "a");
str2int[node_name] = ++node_cnt;
int2str[node_cnt] = node_name;
fprintf(fp, "\t%s;\n", node_name.c_str());
fclose(fp);
return node_cnt;
}
int DrawGraph::Create_Node(int node_name)
{
std::string str_node_name = std::to_string(node_name);
return Create_Node(str_node_name);
}
int DrawGraph::Find_Node(std::string node_name)
{
if (str2int.find(node_name) == str2int.end())
return -1;
return str2int[node_name];
}
std::string DrawGraph::Find_Node(int node_id)
{
if (node_id > node_cnt || node_id <= 0)
return std::string();
return int2str[node_id];
}
int DrawGraph::Create_Relationship(std::string start_name, std::string end_name, int mode, std::string label)
{
FILE* fp = fopen(temp_file.c_str(), "a");
fprintf(fp, "\t%s ", start_name.c_str());
if (mode == DiGraph) {
fprintf(fp, " -> ");
}
else if (mode == Graph) {
fprintf(fp, " -- ");
}
fprintf(fp, " %s ", end_name.c_str());
if (label != std::string("")) {
fprintf(fp, "[label=%s]", label.c_str());
}
fprintf(fp, ";\n");
fclose(fp);
return ++rela_cnt;
}
int DrawGraph::Create_Relationship(int start_id, int end_id, int mode, std::string label)
{
if (Find_Node(start_id) == std::string() || Find_Node(end_id) == std::string())
return -1;
return Create_Relationship(Find_Node(start_id), Find_Node(end_id), mode, label);
}
void DrawGraph::End()
{
FILE* fp = fopen(temp_file.c_str(), "a");
fprintf(fp, "}");
fclose(fp);
}
void DrawGraph::Print_Dot(std::string file_path)
{
std::ifstream in(temp_file.c_str());
std::ofstream out(file_path.c_str());
out << in.rdbuf();
in.close();
out.close();
}
void DrawGraph::Get_Png(std::string file_path)
{
std::string command = "dot -Tpng " + temp_file + " -o " + file_path;
system(command.c_str());
}