在程序内部使用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的代碼:

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;
}
解析svg


最後顯示:

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();
    }
}
顯示圖

完整的代碼:

#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);
};
MyGraphvizGraph.h
#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;
}
MyGraphvizGraph.cpp
#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;
NodeEdge.h
#pragma once

#include "NodeEdge.h"

#include <vector>
using std::vector;

class IGraphicvizView
{
public:
    virtual const TPNodeList& GetNodes () = 0;
    virtual const TPEdgeList& GetEdges () = 0;
};
IGraphicvizView.h

 

使用:

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)如果文字長度超過圖形長度,有可能會將圖形擴寬,以便顯示完整的文字。

 

畢。

posted @ 2014-02-20 10:19  夜雨無聲  阅读(1816)  评论(0编辑  收藏  举报