Я реализовал древовидную структуру данных, в которой каждый узел (рекурсивно) содержит список указателей на своих детей.
Я пытаюсь вычислить (x, y) координаты для визуализации дерева.
Я прошел эту статью:
http://gbook.org/projects/RadialTreeGraph.pdf
Cut Я не могу понять, как пройти мимо первого уровня, то есть это то, что я написал до сих пор:
for (int i = 0; i < GetDepth()+1; i++)
{
if (i == 0)
{
GetNodesInDepth(i).at(0)->SetXRadial(MIDDLE(m_nWidth));
GetNodesInDepth(i).at(0)->SetYRadial(MIDDLE(m_nHeight));
continue;
}
double dNodesInDepth = GetNodesInDepth(i).size();
double dAngleSpace = 2 * PI / dNodesInDepth;
for (int j = 0; j < dNodesInDepth; j++)
{
Node * pCurrentNode = GetNodesInDepth(i).at(j);
pCurrentNode->SetXRadial((SPACING * i) * qCos(j * dAngleSpace) + MIDDLE(m_nWidth));
pCurrentNode->SetYRadial((SPACING * i) * qSin(j * dAngleSpace) + MIDDLE(m_nHeight));
pCurrentNode->m_dAngle = dAngleSpace * j;
if (pCurrentNode->IsParent())
{
//..(I'm stuck here)..//
}
}
}
Я не уверен, как рассчитать лимиты, биссектрисы и т. Д.
это то, что мой ящик сделал до сих пор:
что, очевидно, не то, что я ищу, так как второй (основанный на 0) уровень.
У меня есть доступ к любой информации, которая мне нужна, чтобы получить то, что я ищу.
Вероятно, теперь вы сами это поняли. Если нет, то здесь
double dNodesInDepth = GetNodesInDepth(i).size();
double dAngleSpace = 2 * PI / dNodesInDepth;
вы устанавливаете угол наклона в PI (180 градусов) на втором уровне, так как на этом уровне есть только два узла, dNodesInDepth = 2
, Вот почему после отрисовки узла 20 узел 30 находится на расстоянии 180 градусов. Этот метод подходит для очень плотных деревьев, потому что этот угол будет небольшим. Но в вашем случае вы хотите, чтобы угол был как можно ближе к углу родительского элемента. Поэтому я предлагаю вам получить угол родительского элемента для узлов на уровне 2 и выше и распределить дочерние элементы таким образом, чтобы они имели угловое пространство sibilingAngle = min(dAngleSpace, PI/10)
, Таким образом, первый ребенок будет иметь точный угол родителя, а остальные дети sibilingAngle
друг от друга Вы поняли идею и, вероятно, пришли с лучшим методом. я использую min
Если у вас слишком много узлов на этом уровне, вы хотите сжать узлы ближе друг к другу.
В статье, на которую вы ссылаетесь, используется решение, показанное на Figure 2 – Tangent and bisector limits for directories
, Мне не очень нравится этот метод, потому что если вы определяете абсолютный угол дочерних элементов, а не относительно родительского, у вас может быть более простое / более чистое решение, которое делает именно то, что этот метод пытается сделать с таким количеством операций.
Обновить:
Следующий код создает это изображение:
Я думаю, что вы можете легко понять, как центрировать дочерние узлы и т. Д.
#include <cairo/cairo.h>
#include <math.h>
#include <vector>
using namespace std;
class Node {
public:
Node() {
parent = 0;
angle = 0;
angleRange = 2*M_PI;
depth = 0;
}
void addChildren(int n) {
for (int i=0; i<n; i++) {
Node* c = new Node;
c->parent = this;
c->depth = depth+1;
children.push_back(c);
}
}
vector<Node*> children;
float angle;
float angleRange;
Node* parent;
int depth;
int x, y;
};
void rotate(float x, float y, float angle, float& nx, float& ny) {
nx = x * cos(angle) - y * sin(angle);
ny = x * sin(angle) + y * cos(angle);
}
void draw(Node* root, cairo_t *cr) {
if (root->parent == 0) {
root->x = root->y = 300;
cairo_arc(cr, root->x, root->y, 3, 0, 2 * M_PI);
}
int n = root->children.size();
for (int i=0; i<root->children.size(); i++) {
root->children[i]->angle = root->angle + root->angleRange/n * i;
root->children[i]->angleRange = root->angleRange/n;
float x, y;
rotate(40 * root->children[i]->depth, 0, root->children[i]->angle, x, y);
root->children[i]->x = 300+x;
root->children[i]->y = 300+y;
cairo_move_to(cr, root->x, root->y);
cairo_line_to(cr, root->children[i]->x, root->children[i]->y);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_stroke(cr);
cairo_arc(cr, 300+x, 300+y, 3, 0, 2 * M_PI);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_stroke_preserve(cr);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_fill(cr);
draw(root->children[i], cr);
}
}
int main(void) {
Node root;
root.addChildren(4);
root.children[0]->addChildren(3);
root.children[0]->children[0]->addChildren(2);
root.children[1]->addChildren(5);
root.children[2]->addChildren(5);
root.children[2]->children[1]->addChildren(2);
root.children[2]->children[1]->children[1]->addChildren(2);
cairo_surface_t *surface;
cairo_t *cr;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 600, 600);
cr = cairo_create(surface);
cairo_rectangle(cr, 0.0, 0.0, 600, 600);
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_fill(cr);
cairo_set_line_width(cr, 2);
for (int i=0; i<6; i++) {
cairo_arc(cr, 300, 300, 40*i, 0, 2 * M_PI);
cairo_set_source_rgb(cr, .5, .5, .5);
cairo_stroke(cr);
}
draw(&root, cr);
cairo_surface_write_to_png(surface, "image.png");
cairo_destroy(cr);
cairo_surface_destroy(surface);
return 0;
}
Обновление 2:
Чтобы вам было проще, вот как центрировать узлы:
for (int i=0; i<root->children.size(); i++) {
float centerAdjust = 0;
if (root->parent != 0) {
centerAdjust = (-root->angleRange + root->angleRange / n) / 2;
}
root->children[i]->angle = root->angle + root->angleRange/n * i + centerAdjust;
root->children[i]->angleRange = root->angleRange/n;
Вот реализация алгоритма из статьи, которая должна работать (примечание: я не скомпилировал его, так как у меня нет других частей вашей программы):
void Tree::CalculateAngles()
{
// IsEmpty() returns true if the tree is empty, false otherwise
if (!IsEmpty())
{
Node* pRoot = GetNodesInDepth(0).at(0);
pRoot->SetAngle(0);
// Relative to the current angle
pRoot->SetTangentLimit(PI);
// Absolute limit
pRoot->SetLowerBisector(-PI);
pRoot->SetHigherBisector(PI);
}
for (int depth = 1; depth < GetDepth() + 1; depth++)
{
double dDepth = (double)depth;
// The last non-leaf node in of the current depth (i.e. node with children)
Node* pPreviousNonleafNode = NULL;
// The first non-leaf node
Node* pFirstNonleafNode = NULL;
// The parent of the previous node
Node* pPreviousParent = NULL;
int indexInCurrentParent = 0;
double dTangentLimt = acos( dDepth / (dDepth + 1.0) );
for (int i = 0; i < GetNodesInDepth(depth).size(); i++)
{
Node* pCurrentNode = GetNodesInDepth(depth).at(i);
Node* pParent = pCurrentNode->GetParent();
if (pParent != pPreviousParent)
{
pPreviousParent = pParent;
indexInCurrentParent = 0;
}
// (GetMaxChildAngle() - GetMinChildAngle()) / GetChildCount()
double angleSpace = pParent->GetAngleSpace();
pCurrentNode->SetAngle(angleSpace * (indexInCurrentParent + 0.5));
pCurrentNode->SetTangentLimit(dTangentLimt);
if (pCurrentNode->IsParent())
{
if (!pPreviousNonleafNode)
{
pFirstNonleafNode = pCurrentNode;
}
else
{
double dBisector = (pPreviousNonleafNode->GetAngle() + pCurrentNode->GetAngle()) / 2.0;
pPreviousNonleafNode->SetHigherBisector(dBisector);
pCurrentNode->SetLowerBisector(dBisector);
}
pPreviousNonleafNode = pCurrentNode;
}
indexInCurrentParent++;
}
if (pPreviousNonleafNode && pFirstNonleafNode)
{
if (pPreviousNonleafNode == pFirstNonleafNode)
{
double dAngle = pFirstNonleafNode->GetAngle();
pFirstNonleafNode->SetLowerBisector(dAngle - PI);
pFirstNonleafNode->SetHigherBisector(dAngle + PI);
}
else
{
double dBisector = PI + (pPreviousNonleafNode->GetAngle() + pFirstNonleafNode->GetAngle()) / 2.0;
pFirstNonleafNode->SetLowerBisector(dBisector);
pPreviousNonleafNode->SetHigherBisector(dBisector);
}
}
}
}
void Tree::CalculatePositions()
{
for (int depth = 0; depth < GetDepth() + 1; depth++)
{
double redius = SPACING * depth;
for (int i = 0; i < GetNodesInDepth(depth).size(); i++)
{
Node* pCurrentNode = GetNodesInDepth(depth).at(i);
double angle = pCurrentNode->GetAngle();
pCurrentNode->SetXRadial(redius * qCos(angle) + MIDDLE(m_nWidth));
pCurrentNode->SetYRadial(redius * qSin(angle) + MIDDLE(m_nHeight));
}
}
}
void Tree::CalculateLayout ()
{
CalculateAngles();
CalculatePositions();
}
double Node::GetAngleSpace()
{
return (GetMaxChildAngle() - GetMinChildAngle()) / GetChildCount();
}
Примечание. Я попытался имитировать ваш стиль кода, чтобы вам не пришлось его реорганизовывать, чтобы он соответствовал другим частям вашей программы.
Постскриптум Если вы обнаружите какие-либо ошибки, пожалуйста, сообщите мне в комментариях — я отредактирую свой ответ.