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

How can i draw a Network Model that represents a time-oriented model of the project?

我想可视化活动及其与这样的网络模型的关系

我有table,想画模型。您建议使用哪种方法来解决此问题?

编辑:

当我向该程序添加节点数据(DataTable 包含超过 100 行的活动和前置任务列)并用作节点资源时,得到了

"Index was out of range. Must be non-negative and less than the size of the collection"

(根据@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));
                    }
                }
            } 
        }
   }

部分数据table的截图:

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

我经常使用 List<T> 并且曾经使用过 Dictionary<float, List<NetNode>>

请注意,这个 post 比 SO 答案通常要长很多;希望对大家有所启发..

让我们从一个节点开始class

  • 应该知道自己要打印的名称其他文本数据
  • 它还需要知道 previous 节点 and 以允许在 both 方向上遍历..
  • ..下一个个节点的列表
  • 它也会知道它在布局中的虚拟位置;绘制时必须缩放以适合给定区域..
  • 并且它应该知道如何绘制自身以及与其邻居的联系。

然后可以在一秒钟内收集和管理这些节点 class,可以分析它们以填充列表和位置。

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

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

第class个节点:

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);
}

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

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

它持有..:[=​​34=]

  • 节点列表和..
  • 起始节点列表。不过,实际上应该只有 一个 ;所以你可能想抛出一个 'start node not unique' exception..
  • 分析节点数据的方法列表。

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

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 承担了大部分工作:首先我们填写它,然后我们遍历它来设置值。 最后 我们将节点向上推到需要的程度 居中 它们..:[=​​34=]

    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 缩放或 'leading' 参数来垂直分布节点,从而为额外的文本腾出更多空间..

更新:

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

我做了一些修改:

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

您还必须确保 pbox 足够大 and/or docked/anchored 以允许使用表单调整大小。