如何绘制表示项目的时间导向模型的网络模型?

时间:2018-10-26 11:28:59

标签: c# draw graph-theory cpm

我想用这样的网络模型形象化活动及其之间的关系

netwok example

我有桌子,想画模型。您推荐使用哪种方法来解决此问题?

编辑:

当我向该程序添加Node Data(一个DataTable包含100多个带有Activity和Predecessors列的行)并将其用作Node资源时,得到

  

“索引超出范围。必须为非负数并且小于集合的大小”

(根据@TaW的回答),

在layoutNodeY()部分中

行: nodes.Values.ElementAt(i)[j].VY = 1f * j - c / 2

NodeChart NC = new NodeChart();

private void Form1_Load(object sender, EventArgs e)
   {

     for (int i = 0; i < sortedtable.Rows.Count - 1; i++)
        {  List<string> pred = sortedtable.Rows[i][2].ToString().Split(',').ToList();
           for (int j = 0; j < sortedtable.Rows.Count - 1; j++)
            {
                foreach (var item in pred)
                {
                    if (item == sortedtable.Rows[j][0].ToString() + "." + sortedtable.Rows[j][1].ToString())
                    {
                        NC.theNodes.Add(new NetNode(sortedtable.Rows[i][0].ToString() + "." + sortedtable.Rows[i][1].ToString(), item));
                    }
                }
            } 
        }
   }

Datatable的屏幕截图的一部分:

enter image description here

1 个答案:

答案 0 :(得分:1)

我建议将尽可能多的复杂性放入数据结构中。

我经常使用List<T>,一次使用Dictionary<float, List<NetNode>>

请注意,此帖子比通常的答案要长得多;我希望这对我们有帮助。

让我们从节点类开始

  • 它应该知道您要打印的自己的名称其他文本数据
  • 它还需要知道上一个节点,以允许在两个方向上遍历。
  • .. 下一个节点的列表
  • 它还将知道其在布局中的虚拟位置;绘制时必须按比例缩放以适合给定区域。
  • 它应该知道如何绘制自身及其与邻居的连接。

然后可以在第二类中收集和管理这些节点,然后可以对其进行分析以填充列表和位置。

这是使用您的数据和一个额外节点的结果:

enter image description here

现在让我们仔细看看代码。

首先选择节点类:

class NetNode
{
    public string Text { get; set; }
    public List<NetNode> prevNodes { get; set; }
    public List<NetNode> nextNodes { get; set; }
    public float VX { get; set; }
    public float VY { get; set; }

    public string prevNodeNames;

    public NetNode(string text, string prevNodeNames)
    {
        this.prevNodeNames = prevNodeNames;
        prevNodes = new List<NetNode>();
        nextNodes = new List<NetNode>();
        Text = text; 
        VX = -1;
        VY = -1;
    }
    ...
}

如您所见,它利用List<T>来保存其自身的列表。它的构造函数采用string,它应包含节点名称列表; NodeChart对象将稍后对其进行解析,因为为此,我们需要完整的节点集。

绘图代码很简单,仅作为概念证明。为了获得更好的曲线,您可以使用DrawCurves并对其进行一些简单的改进,或者添加一些额外的点或构建所需的贝塞尔曲线控制点。

箭头也很便宜;不幸的是,内置端盖不是很好。为了改善这一点,您可以创建一个自定义的,可能带有图形路径。

我们在这里:

public void draw(Graphics g, float scale, float size)
{
    RectangleF r = new RectangleF(VX * scale, VY * scale, size, size);
    g.FillEllipse(Brushes.Beige, r);
    g.DrawEllipse(Pens.Black, r);
    using (StringFormat fmt = new StringFormat()
    { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center})
    using (Font f = new Font("Consolas", 20f))
        g.DrawString(Text, f, Brushes.Blue, r, fmt);

    foreach(var nn in nextNodes)
    {
        using (Pen pen = new Pen(Color.Green, 1f)
        { EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor })
            g.DrawLine(pen, getConnector(this, scale, false, size),
                            getConnector(nn, scale, true, size));
    }
}

PointF getConnector(NetNode n, float scale, bool left, float size)
{
    RectangleF r = new RectangleF(n.VX * scale, n.VY * scale, size, size);
    float x = left ? r.Left : r.Right;
    float y = r.Top + r.Height / 2;
    return new PointF(x, y);
}

您将需要扩展节点类以包含更多文本,颜色,字体等。

上面的draw方法是最长的代码之一。现在让我们看一下NodeChart类。

它保存着..:

  • 节点列表和..
  • 启动节点列表。不过,实际上应该只一个;因此您可能要抛出“起始节点不是唯一的” 例外。.
  • 分析节点数据的方法列表。

我遗漏了与将图形适合到给定区域以及任何错误检查有关的任何内容。

class NodeChart
{
    public List<NetNode> theNodes { get; set; }
    public List<NetNode> startNodes { get; set; }

    public NodeChart()
    {
        theNodes = new List<NetNode>();
        startNodes = new List<NetNode>();
    }
    ..

}

第一种方法使用上一个节点的名称解析字符串:

public void fillPrevNodes()
{
    foreach (var n in theNodes)
    {
        var pn = n.prevNodeNames.Split(',');
        foreach (var p in pn)
        {
            var hit = theNodes.Where(x => x.Text == p);
            if (hit.Count() == 1) n.prevNodes.Add(hit.First());
            else if (hit.Count() == 0) startNodes.Add(n);
            else Console.WriteLine(n.Text + ": prevNodeName '" + p + 
                                            "' not found or not unique!" );
        }
    }
}

下一个方法填写了nextNodes列表:

public void fillNextNodes()
{
    foreach (var n in theNodes)
    {
        foreach (var pn in n.prevNodes) pn.nextNodes.Add(n); 
    }
}

现在我们有了数据,需要对节点进行布局。 水平布局很简单,但与分支数据一样,通常需要递归:

public void layoutNodeX()
{
    foreach (NetNode n in startNodes) layoutNodeX(n, n.VX + 1);
}

public void layoutNodeX(NetNode n, float vx)
{
    n.VX = vx;
    foreach (NetNode nn in n.nextNodes)   layoutNodeX(nn, vx + 1);
}

垂直的布局稍微复杂一些。它为每个x位置计数个节点,并将它们平均分布。 Dictionary承担了大部分工作:首先我们将其填写,然后我们对其进行循环以设置值。 最后,我们将节点向上推以使其居中。.:

    public void layoutNodeY()
    {
        NetNode n1 = startNodes.First();
        n1.VY = 0;
        Dictionary<float, List<NetNode>> nodes = 
                          new Dictionary<float, List<NetNode>>();

        foreach (var n in theNodes)
        {
            if (nodes.Keys.Contains(n.VX)) nodes[n.VX].Add(n);
            else nodes.Add(n.VX, new List<NetNode>() { n });
        }

        for (int i = 0; i < nodes.Count; i++)
        {
            int c = nodes[i].Count;
            for (int j = 0; j < c; j++)
            {
                nodes.Values.ElementAt(i)[j].VY = 1f * j - c / 2;
            }
        }

        float min = theNodes.Select(x => x.VY).Min();
        foreach (var n in theNodes) n.VY -= min;
    }

在这里将其包装起来是我从FormPictureBox的称呼:

NodeChart NC = new NodeChart();

private void Form1_Load(object sender, EventArgs e)
{
    NC.theNodes.Add(new NetNode("A",""));
    NC.theNodes.Add(new NetNode("B","A"));
    NC.theNodes.Add(new NetNode("C","B"));
    NC.theNodes.Add(new NetNode("D","B"));
    NC.theNodes.Add(new NetNode("T","B"));
    NC.theNodes.Add(new NetNode("E","C"));
    NC.theNodes.Add(new NetNode("F","D,T"));
    NC.theNodes.Add(new NetNode("G","E,F"));

    NC.fillPrevNodes();
    NC.fillNextNodes();
    NC.layoutNodeX();
    NC.layoutNodeY();

    pictureBox1.Invalidate();
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
    if (NC.theNodes.Count <= 0) return;
    e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
    foreach (var n in NC.theNodes) n.draw(e.Graphics, 100, 33);
}

除了已经提到的内容外,您可能还需要添加y-scale或'lead'参数来垂直分布节点,以便为多余的文本留出更多空间。

更新

这是您提供的数据的结果:

enter image description here

我做了一些更改:

  • 我将第二个“ 35.2”更改为“ 35.3”。您可能的意思是“ 25.2”,但这带来了更多的数据错误。您应该照顾好他们!检查输出窗格!
  • n.draw(e.Graphics, 50, 30);事件中,我已将标度更改为Paint
  • 最后,我在NetNode.draw中将字体大小更改为Font("Consolas", 10f)

您还必须确保pbox足够大和/或停靠/固定以允许调整表单的大小。