我建议将尽可能多的复杂性放在数据结构中。
我经常利用
List<T>
Dictionary<float, List<NetNode>>
注意这篇文章比通常的答案要长得多;我希望它有启发性。。
-
它应该知道自己的
和
-
节点
和
允许转换为
二者都
-
…一份
节点
-
虚拟位置
在布局上;绘制时必须按比例缩放以适合给定区域。。
-
以及与邻国的联系。
以下是使用数据和一个额外节点得到的结果:
现在让我们更仔细地研究一下代码。
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;
}
...
}
列表<T>
string
后来
由
NodeChart
DrawCurves
用几个额外的点或构造所需的贝塞尔控制点。
箭头也是便宜的;不幸的是,内置端盖不是很好。为了提高性能,您可以创建一个自定义的,也许可以使用一个graphicspath。。
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);
}
您需要扩展node类以包含更多的文本、颜色、字体等。。
上面的draw方法是最长的代码之一。让我们看看
现在上课。
它包含..:
-
节点和..的列表。。
-
开始节点的列表。真的应该只有
一
'开始节点不唯一'
..
-
方法列表
节点数据。
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位置的节点,并将它们平均展开。A
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;
}
Form
用一个
PictureBox
:
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”,但这会带来更多的数据错误;你应该照顾好他们!一定要检查输出窗格!!
-
我把天平换成了
n.draw(e.Graphics, 50, 30);
在
Paint
-
最后我把字号改成
Font("Consolas", 10f)
您还必须确保pbox足够大和/或停靠/锚定以允许使用窗体调整大小。