在程序内部使用winGraphviz进行图形自动布局
winGraphviz支持內部圖形形狀進行佈局圖輸出。當然,在我們程序內部並不需要這樣的一個圖,因為我們的圖可能需要其它的繪製元素,而且我們還會在圖形上進行拖動、放大、縮小等功能,一張簡單的佈局圖是不符合我們的要求的。
幸好winGraphviz支持佈局格局的輸出,牠可以告訴我們每一個具體的圖形的位置,每一條連線的位置,只要我們獲取到該位置,即可以用該位置進行設置我們內部的圖形的位置了,這樣,我們就可以達到我們的目的了。
對於圖形坐標的輸出,winGraphviz提供有兩個接口,一個是ToPlain,另一個是ToSvg。我在研究StarUML時,發現牠是使用ToPlain的,但我在程序內部使用ToPlain時,發現坐標出入甚大,不明其原理如何,且因StarUML使用DELPHI編寫,代碼也稍顯複雜,而且基於時間問題,我并未深入研究,總之ToPlain並不能產生確切的坐標,可能還需要其它處理。不過ToSvg則提供了正確的坐標位置,使用它將可以得到我們想要的坐標,確切的坐標。牠是一個XML格式,讀者可以使用一個簡單的圖形,如Graphic g {a -> b -> c; } 來查看輸出的Svg格式是如何樣子,當然接下來我會告訴讀者如何使用COM進行加載winGraphviz,可以先看看我用程序自己畫的佈局圖:
左邊是winGraphviz生成的,右邊是我自己畫的。
加載COM接口:
USES_CONVERSION; IDOT* m_pDot HRESULT hr; hr = CoInitialize(NULL); if (FAILED (hr)) { ASSERT(0); return false; } hr = CoCreateInstance(CLSID_DOT, NULL, CLSCTX_ALL, IID_IDOT, reinterpret_cast<LPVOID*>(&m_pDot)); if (FAILED (hr)) { return false; } return true;
將用戶自定義的節點、連線轉換成winGraphviz語言(假設使用是一個vector的節點和一個vector的連線):
CMyGraphvizGraph* CDiagramLayout::LayoutDiagram( IGraphicvizView* pView, bool fLeftToRight /*= true*/ ) { if (m_pDot == NULL || pView == NULL) { return NULL; } m_pView = pView; string strGraphviz ("digraph G { ordering=out;"); if (fLeftToRight) { strGraphviz += " rankdir=LR;"; } else { strGraphviz += " rankdir=TB;"; } const TPNodeList& vctNodes = pView->GetNodes(); for (size_t i = 0; i < vctNodes.size(); ++i) { strGraphviz += GetNodeString (vctNodes [i]); } const TPEdgeList& vctEdges= pView->GetEdges(); for (size_t i = 0; i < vctEdges.size(); ++i) { strGraphviz += GetEdgeString (vctEdges [i]); } strGraphviz += "}"; return GenerateGraph(strGraphviz.c_str()); } CMyGraphvizGraph* CDiagramLayout::GenerateGraph( const char* pszToParse) { BSTR bsSvg = NULL; HRESULT hr = m_pDot->ToSvg(A2BSTR(pszToParse), &bsSvg); //BSTR bsPlain = NULL; //m_pDot->ToPlain(A2BSTR(pszToParse), &bsPlain); #ifdef _DEBUG IBinaryImage* image = NULL; hr = m_pDot->ToPNG(A2BSTR(pszToParse), &image); if (FAILED (hr)) { ASSERT (0); return NULL; } VARIANT_BOOL fRet = FALSE; hr = image->Save(L"d:\\a.png", &fRet); image->Release(); if (FAILED (hr) || !fRet) { ASSERT(0); return false; } #endif int nLen = SysStringLen(bsSvg); //not including NULL int nBytes = WideCharToMultiByte(CP_ACP, 0, bsSvg, nLen, NULL, NULL, NULL, NULL); //number of bytes not including NULL BSTR bstrA = SysAllocStringByteLen(NULL, nBytes); // allocates nBytes VERIFY(WideCharToMultiByte(CP_ACP, 0, bsSvg, nLen, (LPSTR)bstrA, nBytes, NULL, NULL) == nBytes); CMyGraphvizGraph* pGraph = m_parser.Parse((char*)bstrA); ::SysFreeString(bsSvg); ::SysFreeString(bstrA); return pGraph; } string CDiagramLayout::GetEdgeString( GraphvizEdge* pEdge ) { static char szRet [128]; sprintf (szRet, "%s -> %s [label=%s];", pEdge->strHead.c_str(), pEdge->strTail.c_str(), pEdge->strName.c_str()); return szRet; } string CDiagramLayout::GetNodeString( GraphvizNode* pNode ) { static char szRet [512]; sprintf (szRet, "%s [shape=box,width=%4.3f,height=%4.3f];", pNode->strName.c_str(), (float)pNode->iWidth / PIXEL_PER_INCH_WIDTH (), (float)pNode->iHeight / PIXEL_PER_INCH_HEIGHT ()); return szRet; }
關於解析svg的代碼:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
CMyGraphvizGraph* CGraphvizPlainOutputParser::ParseSvgText( const char* pszText ) { TiXmlDocument doc; doc.Parse(pszText); if (doc.Error()) { return NULL; } CMyGraphvizGraph* pGraph = new CMyGraphvizGraph; TiXmlElement* pRoot = doc.RootElement(); const char* pszWidth = pRoot->Attribute("width"); const char* pszHeight = pRoot->Attribute("height"); if (pszWidth != NULL && pszHeight != NULL) { pGraph->SetWidth(atoi (pszWidth)); pGraph->SetHeight(atoi (pszHeight)); for (TiXmlElement* pItem = pRoot->FirstChildElement ()->FirstChildElement(); pItem != NULL; pItem = pItem->NextSiblingElement()) { const char* pszClass = pItem->Attribute("class"); if (pszClass == NULL) { continue; } if (stricmp (pszClass, "node") == 0) { TiXmlElement* pTitle = pItem->FirstChildElement("text"); TiXmlElement* pPolygon = pItem->FirstChildElement("polygon"); if (pTitle != NULL && pPolygon != NULL) { const char* pszName = pTitle->GetText(); const char* pszPts = pPolygon->Attribute("points"); if (pszName == NULL || pszPts == NULL) { assert (0); continue; } vector <string> vctPoints = split(pszPts, ", "); GraphvizNode* pNode = new GraphvizNode; pNode->strName = pszName; int iTop = 9999; int iBottom = 0; int iLeft = 9999; int iRight = 0; int iX, iY; for (size_t i = 0; i < vctPoints.size(); i += 2) { iX = atoi (vctPoints [i].c_str()); iY = atoi (vctPoints [i + 1].c_str()); if (iX < iLeft) iLeft = iX; if (iY < iTop) iTop = iY; if (iX > iRight) iRight = iX; if (iY > iBottom) iBottom = iY; } pNode->iLeft = iLeft; pNode->iTop = iTop; pNode->iWidth = iRight - iLeft; pNode->iHeight = iBottom - iTop; pGraph->AddNode(pNode); } } else if (stricmp (pszClass, "edge") == 0) { TiXmlElement* pTitle = pItem->FirstChildElement("text"); TiXmlElement* pPath = pItem->FirstChildElement("path"); TiXmlElement* pPolygon = pItem->FirstChildElement("polygon"); if (pTitle != NULL && pPath != NULL && pPolygon != NULL) { const char* pszName = pTitle->GetText(); const char* pszPts = pPolygon->Attribute("points"); const char* pszPath = pPath->Attribute("d"); if (pszName == NULL || pszPts == NULL || pszPath == NULL) { assert (0); continue; } vector <string> vctPathPoints = split(pszPath, "MC, "); vector <string> vctPolygonPoints = split(pszPts, ", "); GraphvizEdge* pEdge = new GraphvizEdge; pEdge->strName = pszName; for (size_t i = 0; i < vctPathPoints.size(); i += 2) { POINT pt = {atoi (vctPathPoints [i].c_str()), atoi (vctPathPoints [i + 1].c_str())}; pEdge->ptsLine.push_back(pt); } for (size_t j = 0; j < vctPolygonPoints.size(); j += 2) { POINT pt = {atoi (vctPolygonPoints [j].c_str()), atoi (vctPolygonPoints [j + 1].c_str())}; pEdge->ptsArrow.push_back(pt); } pGraph->AddEdge(pEdge); } } } } return pGraph; }
最後顯示:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void CGraphDlg::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: Add your message handler code here // Do not call CDialog::OnPaint() for painting messages if (m_pGraph != NULL) { CDC dcMem; CBitmap bmp; dcMem.CreateCompatibleDC(&dc); bmp.CreateCompatibleBitmap(&dc, m_pGraph->GetWidth(), m_pGraph->GetHeight()); dcMem.SelectObject(&bmp); const TPNodeList& vctNodes = m_pGraph->GetNodes(); const TPEdgeList& vctEdges = m_pGraph->GetEdges(); for (size_t i = 0; i < vctNodes.size(); ++i) { RECT rc = { vctNodes [i]->iLeft, vctNodes [i]->iTop, vctNodes [i]->iWidth + vctNodes [i]->iLeft, vctNodes [i]->iHeight + vctNodes [i]->iTop }; dcMem.FillSolidRect(&rc, RGB (128, 128, 128)); CString str (vctNodes [i]->strName.c_str()); dcMem.DrawText (str, &rc, DT_VCENTER | DT_SINGLELINE | DT_CENTER); } CPen pen (PS_SOLID, 1, RGB (255, 255, 255)); CBrush br (RGB (255, 255, 255)); dcMem.SelectObject(&pen); dcMem.SelectObject(&br); for (size_t j = 0; j < vctEdges.size(); ++j) { size_t iLineSize = vctEdges [j]->ptsLine.size(); POINT* pptLine = new POINT [iLineSize]; for (size_t k = 0; k < iLineSize; ++k) { pptLine [k] = vctEdges [j]->ptsLine [k]; } dcMem.PolyBezier(pptLine, iLineSize); // 箭头 size_t iArrowSize = vctEdges [j]->ptsArrow.size(); POINT* pptsArrow = new POINT [iArrowSize]; for (size_t m = 0; m < iArrowSize; ++m) { pptsArrow [m] = vctEdges [j]->ptsArrow [m]; } dcMem.Polygon(pptsArrow, iArrowSize); delete [] pptsArrow; delete [] pptLine; } dc.BitBlt(0, 0, m_pGraph->GetWidth(), m_pGraph->GetHeight(), &dcMem, 0, 0, SRCCOPY); bmp.DeleteObject(); dcMem.DeleteDC(); } }
完整的代碼:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#pragma once #include "NodeEdge.h" #include "WinGraphviz/WinGraphviz.h" #include "IGraphicvizView.h" #include <vector> #include <string> using std::vector; using std::string; class CGraphvizPlainOutputParser; class CMyGraphvizGraph { friend class CGraphvizPlainOutputParser; private: int m_iWidth; int m_iHeight; TPNodeList m_nodes; TPEdgeList m_edges; public: CMyGraphvizGraph (); ~CMyGraphvizGraph(); GraphvizNode* GetNode (size_t iIndex); size_t GetNodeCount () const; GraphvizEdge* GetEdge (size_t iIndex); size_t GetEdgeCount () const; const TPNodeList& GetNodes () { return m_nodes; }; const TPEdgeList& GetEdges () { return m_edges; } int GetWidth () { return m_iWidth; }; int GetHeight () { return m_iHeight; }; protected: void SetWidth (int iWidth); void SetHeight (int iHeight); void AddNode( GraphvizNode* pNode ); void AddEdge( GraphvizEdge* pEdge ); }; class CGraphvizPlainOutputParser { private: vector <string> m_vctTempStr; private: double InchToPixel (double dblInch, BOOL fWidth); double InchStrToPixel (const char* pszInch, BOOL fWidth); void ParseLine (const char* pszLine, vector <string>& vctOut); CMyGraphvizGraph* ParseGraphicLine (const char* pszLine); GraphvizNode* ParseNodeLine (const char* pszLine); GraphvizEdge* ParseEdgeLine (const char* pszLine); void SeparateLine( const char* pszText, std::vector<string>& vctPlainOut ); public: CGraphvizPlainOutputParser (); ~CGraphvizPlainOutputParser(); CMyGraphvizGraph* Parse (const char* pszText); CMyGraphvizGraph* ParsePlainText( const char* pszText ); CMyGraphvizGraph* ParseSvgText( const char* pszText ); }; class CDiagramLayout { private: IDOT* m_pDot; // viz图 IGraphicvizView* m_pView; // 显示图 CGraphvizPlainOutputParser m_parser; // 解析器,用于分析viz布局后的图 bool Init(); string GetNodeIdString (); string GetEdgeIdString (); CMyGraphvizGraph* GenerateGraph (const char* pszToParse); string GetEdgeString( GraphvizEdge* pEdge ); string GetNodeString( GraphvizNode* pNode ); public: CDiagramLayout (); ~CDiagramLayout(); CMyGraphvizGraph* LayoutDiagram (IGraphicvizView* pView, bool fLeftToRight = true); };
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include "stdafx.h" #include "MyGraphvizGraph.h" #include "tinyxml/tinyxml.h" #include <shlwapi.h> #include <math.h> #pragma comment (lib, "shlwapi.lib") #pragma warning (disable : 4996) int PIXEL_PER_INCH_WIDTH () { HWND hwnd = GetDesktopWindow(); HDC hdc = GetDC(hwnd); int iPixel = GetDeviceCaps (hdc, LOGPIXELSX); ReleaseDC(hwnd, hdc); return iPixel; } int PIXEL_PER_INCH_HEIGHT () { HWND hwnd = GetDesktopWindow(); HDC hdc = GetDC(hwnd); int iPixel = GetDeviceCaps (hdc, LOGPIXELSY); ReleaseDC(hwnd, hdc); return iPixel; } //字符串分割函数 static std::vector<std::string> split(const char* pszToSplit, const char* pszSpplitor) { std::vector<std::string> result; char* pszTemp = strdup (pszToSplit); char* token = strtok(pszTemp , pszSpplitor ); while( token != NULL ) { /* While there are tokens in "string" */ result.push_back(token); /* Get next token: */ token = strtok( NULL, pszSpplitor ); } free (pszTemp); return result; } GraphvizNode* CMyGraphvizGraph::GetNode( size_t iIndex ) { if (iIndex >= m_nodes.size()) { return NULL; } return m_nodes [iIndex]; } size_t CMyGraphvizGraph::GetNodeCount() const { return m_nodes.size(); } GraphvizEdge* CMyGraphvizGraph::GetEdge( size_t iIndex ) { if (iIndex >= m_edges.size()) { return NULL; } return m_edges [iIndex]; } size_t CMyGraphvizGraph::GetEdgeCount() const { return m_edges.size(); } CMyGraphvizGraph::CMyGraphvizGraph() : m_iWidth (0) , m_iHeight (0) { } CMyGraphvizGraph::~CMyGraphvizGraph() { for (size_t i = 0; i < m_nodes.size(); ++i) { delete m_nodes [i]; } m_nodes.clear(); for (size_t j = 0; j < m_edges.size(); ++j) { delete m_edges [j]; } m_edges.clear(); } void CMyGraphvizGraph::SetWidth( int iWidth ) { m_iWidth = iWidth; } void CMyGraphvizGraph::SetHeight( int iHeight ) { m_iHeight = iHeight; } void CMyGraphvizGraph::AddNode( GraphvizNode* pNode ) { m_nodes.push_back(pNode); } void CMyGraphvizGraph::AddEdge( GraphvizEdge* pEdge ) { m_edges.push_back(pEdge); } double CGraphvizPlainOutputParser::InchToPixel( double dblInch, BOOL fWidth ) { return dblInch * (fWidth ? PIXEL_PER_INCH_WIDTH () : PIXEL_PER_INCH_HEIGHT ()); } double CGraphvizPlainOutputParser::InchStrToPixel( const char* pszInch, BOOL fWidth ) { char* pszEnd = NULL; return InchToPixel (strtod (pszInch, &pszEnd), fWidth); } void CGraphvizPlainOutputParser::ParseLine( const char* pszLine, vector <string>& vctOut ) { char chSeparator = ' '; const char* pszNext = NULL; vctOut.clear(); char* pszNewString = strdup (pszLine); StrTrimA (pszNewString, " "); const char* pszTemp = pszNewString; do { pszNext = strchr (pszTemp, chSeparator); if (pszNext != NULL) { vctOut.push_back(string (pszTemp, 0, pszNext - pszTemp)); } else { vctOut.push_back(pszTemp); break; } pszTemp = pszNext + 1; /* ' ' 的下一个字符位置 */ while (*pszTemp == ' ' && *pszTemp != '\0') { ++pszTemp; } } while (pszNext != NULL); free (pszNewString); } CMyGraphvizGraph* CGraphvizPlainOutputParser::ParseGraphicLine( const char* pszLine ) { CMyGraphvizGraph* pGraph = NULL; m_vctTempStr.clear(); ParseLine(pszLine, m_vctTempStr); ASSERT(m_vctTempStr.size() >= 3); pGraph = new CMyGraphvizGraph; pGraph->SetWidth((int)InchStrToPixel(m_vctTempStr [2].c_str(), TRUE)); pGraph->SetHeight((int)InchStrToPixel(m_vctTempStr [3].c_str(), FALSE)); return pGraph; } GraphvizNode* CGraphvizPlainOutputParser::ParseNodeLine( const char* pszLine ) { GraphvizNode* pNode = NULL; m_vctTempStr.clear(); ParseLine(pszLine, m_vctTempStr); pNode = new GraphvizNode; pNode->strName = m_vctTempStr [1]; pNode->iLeft = (int)InchStrToPixel (m_vctTempStr [2].c_str(), TRUE); pNode->iTop = (int)InchStrToPixel (m_vctTempStr [3].c_str(), FALSE); pNode->iWidth = (int)InchStrToPixel (m_vctTempStr [4].c_str(), TRUE); pNode->iHeight = (int)InchStrToPixel (m_vctTempStr [5].c_str(), FALSE); return pNode; } GraphvizEdge* CGraphvizPlainOutputParser::ParseEdgeLine( const char* pszLine ) { GraphvizEdge* pEdge = NULL; int iCount, i, iX, iY; m_vctTempStr.clear(); ParseLine(pszLine, m_vctTempStr); pEdge = new GraphvizEdge; pEdge->strHead = m_vctTempStr [1]; pEdge->strTail = m_vctTempStr [2]; iCount = atoi (m_vctTempStr [3].c_str ()); pEdge->ptsLine.clear(); for (i = 0; i < iCount; ++i) { iX = (int)InchStrToPixel(m_vctTempStr [4 + i * 2].c_str (), TRUE); iY = (int)InchStrToPixel(m_vctTempStr [4 + i * 2 + 1].c_str(), FALSE); POINT pt = {iX, iY}; pEdge->ptsLine.push_back(pt); } pEdge->strName = m_vctTempStr [4 + iCount * 2]; return pEdge; } CGraphvizPlainOutputParser::CGraphvizPlainOutputParser() { } CGraphvizPlainOutputParser::~CGraphvizPlainOutputParser() { } CMyGraphvizGraph* CGraphvizPlainOutputParser::Parse( const char* pszText ) { #ifdef PARSE_PLAIN return ParsePlainText (pszText); #else return ParseSvgText (pszText); #endif } void CGraphvizPlainOutputParser::SeparateLine( const char* pszText, std::vector<string>& vctPlainOut ) { const char* pszTemp = pszText; while (pszTemp != NULL && *pszTemp != '\0') { const char* pszNext = strchr (pszTemp, '\n'); if (pszNext == NULL) { break; } vctPlainOut.push_back(string (pszTemp, 0, pszNext - pszTemp)); pszTemp = pszNext + 1; } } CMyGraphvizGraph* CGraphvizPlainOutputParser::ParsePlainText( const char* pszText ) { static const char* TOKEN4_NODE = "node"; static const char* TOKEN4_EDGE = "edge"; std::vector <string> vctPlainOut; CMyGraphvizGraph* pGraph = NULL; GraphvizNode* pNode = NULL; GraphvizEdge* pEdge = NULL; string strToken; size_t i; // 将多行文本分解成VECTOR SeparateLine (pszText, vctPlainOut); pGraph = ParseGraphicLine(vctPlainOut [0].c_str()); // 解析字符串 for (i = 1; i < vctPlainOut.size(); ++i) { if (strnicmp (vctPlainOut [i].c_str(), TOKEN4_NODE, 4) == 0) { pGraph->AddNode (ParseNodeLine (vctPlainOut [i].c_str())); } else if (strnicmp(vctPlainOut [i].c_str(), TOKEN4_EDGE, 4) == 0) { pGraph->AddEdge (ParseEdgeLine (vctPlainOut [i].c_str())); } } return pGraph; } CMyGraphvizGraph* CGraphvizPlainOutputParser::ParseSvgText( const char* pszText ) { TiXmlDocument doc; doc.Parse(pszText); if (doc.Error()) { return NULL; } CMyGraphvizGraph* pGraph = new CMyGraphvizGraph; TiXmlElement* pRoot = doc.RootElement(); const char* pszWidth = pRoot->Attribute("width"); const char* pszHeight = pRoot->Attribute("height"); if (pszWidth != NULL && pszHeight != NULL) { pGraph->SetWidth(atoi (pszWidth)); pGraph->SetHeight(atoi (pszHeight)); for (TiXmlElement* pItem = pRoot->FirstChildElement ()->FirstChildElement(); pItem != NULL; pItem = pItem->NextSiblingElement()) { const char* pszClass = pItem->Attribute("class"); if (pszClass == NULL) { continue; } if (stricmp (pszClass, "node") == 0) { TiXmlElement* pTitle = pItem->FirstChildElement("text"); TiXmlElement* pPolygon = pItem->FirstChildElement("polygon"); if (pTitle != NULL && pPolygon != NULL) { const char* pszName = pTitle->GetText(); const char* pszPts = pPolygon->Attribute("points"); if (pszName == NULL || pszPts == NULL) { assert (0); continue; } vector <string> vctPoints = split(pszPts, ", "); GraphvizNode* pNode = new GraphvizNode; pNode->strName = pszName; int iTop = 9999; int iBottom = 0; int iLeft = 9999; int iRight = 0; int iX, iY; for (size_t i = 0; i < vctPoints.size(); i += 2) { iX = atoi (vctPoints [i].c_str()); iY = atoi (vctPoints [i + 1].c_str()); if (iX < iLeft) iLeft = iX; if (iY < iTop) iTop = iY; if (iX > iRight) iRight = iX; if (iY > iBottom) iBottom = iY; } pNode->iLeft = iLeft; pNode->iTop = iTop; pNode->iWidth = iRight - iLeft; pNode->iHeight = iBottom - iTop; pGraph->AddNode(pNode); } } else if (stricmp (pszClass, "edge") == 0) { TiXmlElement* pTitle = pItem->FirstChildElement("text"); TiXmlElement* pPath = pItem->FirstChildElement("path"); TiXmlElement* pPolygon = pItem->FirstChildElement("polygon"); if (pTitle != NULL && pPath != NULL && pPolygon != NULL) { const char* pszName = pTitle->GetText(); const char* pszPts = pPolygon->Attribute("points"); const char* pszPath = pPath->Attribute("d"); if (pszName == NULL || pszPts == NULL || pszPath == NULL) { assert (0); continue; } vector <string> vctPathPoints = split(pszPath, "MC, "); vector <string> vctPolygonPoints = split(pszPts, ", "); GraphvizEdge* pEdge = new GraphvizEdge; pEdge->strName = pszName; for (size_t i = 0; i < vctPathPoints.size(); i += 2) { POINT pt = {atoi (vctPathPoints [i].c_str()), atoi (vctPathPoints [i + 1].c_str())}; pEdge->ptsLine.push_back(pt); } for (size_t j = 0; j < vctPolygonPoints.size(); j += 2) { POINT pt = {atoi (vctPolygonPoints [j].c_str()), atoi (vctPolygonPoints [j + 1].c_str())}; pEdge->ptsArrow.push_back(pt); } pGraph->AddEdge(pEdge); } } } } return pGraph; } CDiagramLayout::CDiagramLayout() { Init (); } CDiagramLayout::~CDiagramLayout() { if (m_pDot != NULL) { m_pDot->Release(); m_pDot = NULL; } CoUninitialize(); } bool CDiagramLayout::Init() { m_pView = NULL; m_pDot = NULL; USES_CONVERSION; HRESULT hr; CComBSTR result; hr = CoInitialize(NULL); if (FAILED (hr)) { ASSERT(0); return false; } hr = CoCreateInstance(CLSID_DOT, NULL, CLSCTX_ALL, IID_IDOT, reinterpret_cast<LPVOID*>(&m_pDot)); if (FAILED (hr)) { ASSERT(0); return false; } return true; } CMyGraphvizGraph* CDiagramLayout::LayoutDiagram( IGraphicvizView* pView, bool fLeftToRight /*= true*/ ) { if (m_pDot == NULL || pView == NULL) { return NULL; } m_pView = pView; string strGraphviz ("digraph G { ordering=out;"); if (fLeftToRight) { strGraphviz += " rankdir=LR;"; } else { strGraphviz += " rankdir=TB;"; } const TPNodeList& vctNodes = pView->GetNodes(); for (size_t i = 0; i < vctNodes.size(); ++i) { strGraphviz += GetNodeString (vctNodes [i]); } const TPEdgeList& vctEdges= pView->GetEdges(); for (size_t i = 0; i < vctEdges.size(); ++i) { strGraphviz += GetEdgeString (vctEdges [i]); } strGraphviz += "}"; return GenerateGraph(strGraphviz.c_str()); } CMyGraphvizGraph* CDiagramLayout::GenerateGraph( const char* pszToParse) { BSTR bsSvg = NULL; HRESULT hr = m_pDot->ToSvg(A2BSTR(pszToParse), &bsSvg); //BSTR bsPlain = NULL; //m_pDot->ToPlain(A2BSTR(pszToParse), &bsPlain); #ifdef _DEBUG IBinaryImage* image = NULL; hr = m_pDot->ToPNG(A2BSTR(pszToParse), &image); if (FAILED (hr)) { ASSERT (0); return NULL; } VARIANT_BOOL fRet = FALSE; hr = image->Save(L"d:\\a.png", &fRet); image->Release(); if (FAILED (hr) || !fRet) { ASSERT(0); return false; } #endif int nLen = SysStringLen(bsSvg); //not including NULL int nBytes = WideCharToMultiByte(CP_ACP, 0, bsSvg, nLen, NULL, NULL, NULL, NULL); //number of bytes not including NULL BSTR bstrA = SysAllocStringByteLen(NULL, nBytes); // allocates nBytes VERIFY(WideCharToMultiByte(CP_ACP, 0, bsSvg, nLen, (LPSTR)bstrA, nBytes, NULL, NULL) == nBytes); CMyGraphvizGraph* pGraph = m_parser.Parse((char*)bstrA); ::SysFreeString(bsSvg); ::SysFreeString(bstrA); return pGraph; } string CDiagramLayout::GetEdgeString( GraphvizEdge* pEdge ) { static char szRet [128]; sprintf (szRet, "%s -> %s [label=%s];", pEdge->strHead.c_str(), pEdge->strTail.c_str(), pEdge->strName.c_str()); return szRet; } string CDiagramLayout::GetNodeString( GraphvizNode* pNode ) { static char szRet [512]; sprintf (szRet, "%s [shape=box,width=%4.3f,height=%4.3f];", pNode->strName.c_str(), (float)pNode->iWidth / PIXEL_PER_INCH_WIDTH (), (float)pNode->iHeight / PIXEL_PER_INCH_HEIGHT ()); return szRet; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#pragma once #include <vector> #include <string> using std::vector; using std::string; struct GraphvizNode { string strName; int iLeft; int iTop; int iWidth; int iHeight; }; typedef vector <GraphvizNode*> TPNodeList; struct GraphvizEdge { string strName; string strHead; // head node string strTail; // tail node vector <POINT> ptsLine; vector <POINT> ptsArrow; // used for svg }; typedef vector <GraphvizEdge*> TPEdgeList;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#pragma once #include "NodeEdge.h" #include <vector> using std::vector; class IGraphicvizView { public: virtual const TPNodeList& GetNodes () = 0; virtual const TPEdgeList& GetEdges () = 0; };
使用:
CDiagramLayout m_layout; CMyGraphvizGraph* m_pGraph; InitGraphic (); m_pGraph = m_layout.LayoutDiagram(this, m_fLeftToRight); // ----------------------------------- void CGraphDlg::InitGraphic() { for (int i = 0; i < 5; ++i) { GraphvizNode* pNode = new GraphvizNode; char szName [32]; sprintf (szName, "n%d", i); pNode->strName = szName; pNode->iLeft = 0; pNode->iTop = 0; pNode->iWidth = 64; pNode->iHeight = 64; m_vctNodes.push_back(pNode); } GraphvizEdge* pEdge1 = new GraphvizEdge; pEdge1->strHead = "n1"; pEdge1->strTail = "n0"; pEdge1->strName = "e0"; m_vctEdges.push_back(pEdge1); GraphvizEdge* pEdge2 = new GraphvizEdge; pEdge2->strHead = "n2"; pEdge2->strTail = "n0"; pEdge2->strName = "e1"; m_vctEdges.push_back(pEdge2); GraphvizEdge* pEdge3 = new GraphvizEdge; pEdge3->strHead = "n3"; pEdge3->strTail = "n0"; pEdge3->strName = "e2"; m_vctEdges.push_back(pEdge3); GraphvizEdge* pEdge4 = new GraphvizEdge; pEdge4->strHead = "n4"; pEdge4->strTail = "n3"; pEdge4->strName = "e3"; m_vctEdges.push_back(pEdge4); GraphvizEdge* pEdge5 = new GraphvizEdge; pEdge5->strHead = "n1"; pEdge5->strTail = "n2"; pEdge5->strName = "e4"; m_vctEdges.push_back(pEdge5); //GraphvizEdge* pEdge6 = new GraphvizEdge; //pEdge6->strHead = "n0"; //pEdge6->strTail = "n3"; //pEdge6->strName = "e5"; //m_vctEdges.push_back(pEdge6); }
需要注意的是:
1)winGraphviz語言使用的單位是英吋,所以要獲取屏幕像素與英吋的比例。
2)如果文字長度超過圖形長度,有可能會將圖形擴寬,以便顯示完整的文字。
畢。