Как я могу нарисовать сетевую модель, которая представляет ориентированную на время модель проекта?

Я хочу визуализировать деятельность и ее отношения с такой сетевой моделью

пример сети

У меня есть таблица и я хочу нарисовать модель. Какой метод вы рекомендуете для решения этой проблемы?

Редактировать:

Когда я добавляю данные узла (таблица данных содержит более 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 достаточно большой и / или закреплен / закреплен, чтобы можно было изменять размеры формы.

Другие вопросы по тегам