воскресенье, 1 марта 2015 г.

GraphSharp + QuickGraph или CodeStars. Track 04. Визуализация хоть чего-нибудь

Я выбрала для решения своей задачи QuickGraph потому что не нашла ему альтернатив, кроме самостоятельной реализации алгоритма.
И я хотела добавить своему решению лоска визуализацией. У QuickGraph есть сборка QuickGraph.Glee  Но, судя по всему, ее надо было ставить дополнительно.
А у GraphSharp есть WPF контроллер... и он использует QuickGraph. "Сказка же!" - думаю я: минимум зависимостей, все аккуратненько. С контроллером. Легко и просто...

Но пришлось создавать отдельный пост, потому что просто не получилось.

Все, казалось бы просто. У меня уже есть граф. Я его посчитала и теперь хочу отобразить.
Тем временем, чтобы отобразить граф с помощью Graph# есть два способа.

Визуализация графа Graph#

Простой способ.
Мануал: http://www.youtube.com/watch?v=VTbuvkaPGxE
Встроенный WPF контроллер GraphLayout  позволяет привязать к себе IBidirectionalGraph<object, IEdge<object>>. Что и делают в имеющемся мануале.
Но только его.
Нюанс в том, что, например BidirectionalGraph<object, Edge<object>> не может быть преобразован  к данному интерфейсу никаким приведением.
В то же время алгоритмы кратчайшего пути из QuickGraph работают именно с графами, а не интерфейсами.

В случае, если нас это не устраивает и хочется чего-то более сложного, мы можем воспользоваться вторым вариантом.

Полный и абсолютно настраиваемый способ. 
Мануал:  https://sachabarbs.wordpress.com/2010/08/31/pretty-cool-graphs-in-wpf/
Мы описываем с самого начала тип данных вершины, тип данных ребра, тип графа (наследуем от Bidirectional...), свой Layout для отображения (наследуем от встроенного) и далее по тексту.

Это трудоемко, но можно попробовать.

И все было бы хорошо, если бы не процесс заполнения. В исходных данных у меня перечислены ребра и смежные с ними вершины. Одна вершина может упоминаться для разных ребер, а добавить в граф мне ее надо единожды. И либо перерабатывать исходные данные.. либо, просто проверять. И я добавляю новую вершину только в том случае, если вершины с таким значением еще нет.

if (!graph.ContainsVertex(language_from))
   graph.AddVertex(language_from);

А это прекрасно работает, только пока для класса реализующего вершины определены интерфейсы и методы сравнения... нюанс в том, что я понятия не имею какие именно этому методу нужны интерфейсы от моего класса вершины.

Визуализация произвольного графа с базовыми типами в Graph#

Не могу гарантировать, что это работает действительно для любого графа. Но мой, к счастью, достаточно прост и никаких граблей на моем пути не попалось.

Мне надо сначала заполнить граф, потом посчитать, потом отобразить.
Заполнение и расчет требуют одних интерфейсов, а отображение другого (вполне конкретного).

Поэтому я не нашла ничего лучше, чем перед отображением копировать граф в новую структуру, приспособленную к GraphLayout:

private void ShowGraph(BidirectionalGraph<object, Edge<object>> graph)
{
  var graphForVis = new BidirectionalGraph<object, IEdge<object>>();

  foreach(object node in graph.Vertices)
  {
     graphForVis.AddVertex(node);
  }
  foreach (Edge<object> edge in graph.Edges)
  {
     graphForVis.AddEdge(new Edge<object>(edge.Source, edge.Target));
  }

  //this.LanguageGraphCanvas - мой LayoutGraph контроллер
  this.LanguageGraphCanvas.Graph = graphForVis;
}       

Буду считать это такой разновидностью контроллера между моделью данных (QuickGraph) и их представлением (Graph#). 

Плюсы такого решения:

  1. Я, в процессе экспериментов, изменила тип графа на Bidirectional. Но могла бы этого и не делать. Как следствие: мне не пришлось ради отображения менять данные и механику работы с ними.
  2. Минимальное количество дополнительного кода. Описание классов вершины, ребра, графа... - очень объемно и трудоемко, т.е. избыточно, при том что мне никаких особых свойств не надо.
  3. Так можно отобразить если не любой граф, то достаточное их количество и при этом решить проблему с совместимостью типа графа с мат. методами QuickGraph.
Минусы:
  1. this.LanguageGraphCanvas.Graph = graphForVis; 
    Привязка данных к WPF контроллеру из кода скорее моветон, чем норма. Но чтобы привязка была человеческая тип графа должен бы поддерживать INotifyPropertyChanged - как в примере - чтобы отображение привязанного сразу из XAML графа изменилось после заполнения. 


Ссылки (cобраны из обоих постов в кучку):

1. Визуализация средствами QuickGraph.Glee http://quickgraph.codeplex.com/wikipage?title=Visualization%20Using%20Glee .
2. Домашняя страница и конкретно мануалы по Graph#  http://graphsharp.codeplex.com/wikipage?title=Tutorials&referringTitle=Home
3. Пример работы QuickGraph и Graphiz
http://dotnet.wonderu.com/2009/05/blog-post.html

Картинка, Проект. Музычка





Комментариев нет:

Отправить комментарий