Как я могу нарисовать сетевую модель, которая представляет ориентированную на время модель проекта?
Я хочу визуализировать деятельность и ее отношения с такой сетевой моделью
У меня есть таблица и я хочу нарисовать модель. Какой метод вы рекомендуете для решения этой проблемы?
Редактировать:
Когда я добавляю данные узла (таблица данных содержит более 100 строк с столбцами действий и предшественников) в эту Программу и, используя ресурсы узла, получаю
"Индекс был вне диапазона. Должен быть неотрицательным и меньшим, чем размер коллекции"
(согласно ответу @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:
1 ответ
Я рекомендую поместить как можно больше сложности в структуры данных.
Я много пользуюсь List<T>
и в одно время Dictionary<float, List<NetNode>>
Обратите внимание, что этот пост намного длиннее, чем обычно ответы SO; Я надеюсь, что это поучительно..
Давайте начнем с класса узла
- Он должен знать свое имя и другие текстовые данные, которые вы хотите распечатать
- Также необходимо знать предыдущие узлы и разрешить переход в обоих направлениях.
- .. список следующих узлов
- Он также будет знать свою виртуальную позицию в макете; это должно быть масштабировано при рисовании, чтобы соответствовать данной области.
- И надо уметь рисовать себя и связи с соседями.
Эти узлы затем могут быть собраны и управляться во втором классе, который может анализировать их, чтобы заполнить списки и позиции.
Вот результат, используя ваши данные плюс один дополнительный узел:
Теперь давайте ближе рассмотрим код.
Класс узла сначала:
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);
}
Вы захотите расширить класс узла, включив в него больше текста, цветов, шрифтов и т. Д.
Приведенный выше метод рисования является одним из самых длинных фрагментов кода. Давайте посмотрим на 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;
}
Чтобы обернуть это здесь, как я называю это с 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 или "ведущий", чтобы развернуть узлы по вертикали, чтобы освободить место для дополнительного текста.
Обновить:
Вот результат предоставленных вами данных:
Я сделал несколько изменений:
- Я изменил 2-й "35,2" на "35,3". Возможно, вы имели в виду "25,2", но это вызывает больше ошибок в данных; Вы должны заботиться о них всех! Проверьте панель вывода!!
- Я изменил весы на
n.draw(e.Graphics, 50, 30);
вPaint
событие - И наконец я изменил размер шрифта на
Font("Consolas", 10f)
в NetNode.draw.
Вы также должны убедиться, что pbox достаточно большой и / или закреплен / закреплен, чтобы можно было изменять размеры формы.