Commit 6aaea53a authored by Dilson Guimarães's avatar Dilson Guimarães Committed by Caio Tonetti
Browse files

Graph layout plugin added, with a force based layout algorithm.

GUI:
parent 41ab6852
......@@ -24,3 +24,4 @@
ecm_optional_add_subdirectory(assignvalues)
ecm_optional_add_subdirectory(generategraph)
ecm_optional_add_subdirectory(transformedges)
ecm_optional_add_subdirectory(graphlayout)
# Copyright 2020 Dilson Almeida Guimarães <dilsonguim@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Boost requires exceptions for this plugin
kde_enable_exceptions()
set(graphlayout_SRCS
graphlayoutplugin.cpp
graphlayoutwidget.cpp
../../logging.cpp
)
#boost requires exceptions
kde_source_files_enable_exceptions(graphlayoutplugin.cpp)
ki18n_wrap_ui(graphlayout_SRCS graphlayoutwidget.ui)
add_library(graphlayoutplugin
MODULE
${graphlayout_SRCS}
)
target_link_libraries(graphlayoutplugin
PUBLIC
rocsgraphtheory
KF5::Completion
)
ecm_optional_add_subdirectory(autotests)
install(TARGETS graphlayoutplugin DESTINATION ${PLUGIN_INSTALL_DIR}/rocs/editorplugins)
/*
* Copyright 2020 Dilson Almeida Guimarães <dilsonguim@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3, or any
* later version accepted by the membership of KDE e.V. (or its
* successor approved by the membership of KDE e.V.), which shall
* act as a proxy defined in Section 6 of version 3 of the license.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <https://www.gnu.org/licenses/>.
*/
#include "graphlayoutplugin.h"
#include "graphlayoutwidget.h"
#include "typenames.h"
#include "graphdocument.h"
#include "logging_p.h"
#include <KPluginFactory>
#include <QDialog>
using namespace GraphTheory;
K_PLUGIN_FACTORY_WITH_JSON( EditorPluginFactory,
"graphlayoutplugin.json",
registerPlugin<GraphLayoutPlugin>();)
class GraphTheory::GraphLayoutPluginPrivate
{
public:
GraphLayoutPluginPrivate()
: m_dialog(0)
{
}
~GraphLayoutPluginPrivate()
{
m_dialog->deleteLater();
}
QDialog *m_dialog;
};
GraphLayoutPlugin::GraphLayoutPlugin(QObject* parent, const QList<QVariant> & /* args*/)
: EditorPluginInterface("rocs_graphlayoutplugin", parent)
, d(new GraphLayoutPluginPrivate)
{
}
GraphLayoutPlugin::~GraphLayoutPlugin()
{
}
void GraphLayoutPlugin::showDialog(GraphDocumentPtr document)
{
if (!document) {
qCCritical(GRAPHTHEORY_GENERAL) << "No valid graph document given, aborting.";
}
QPointer<GraphLayoutWidget> dialog = new GraphLayoutWidget(document);
dialog->exec();
return;
}
#include "graphlayoutplugin.moc"
/*
* Copyright 2020 Dilson Almeida Guimarães <dilsonguim@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3, or any
* later version accepted by the membership of KDE e.V. (or its
* successor approved by the membership of KDE e.V.), which shall
* act as a proxy defined in Section 6 of version 3 of the license.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GRAPHLAYOUTPLUGIN_H
#define GRAPHLAYOUTPLUGIN_H
#include "editorplugins/editorplugininterface.h"
class QObject;
namespace GraphTheory
{
class GraphLayoutPluginPrivate;
class GraphLayoutPlugin : public EditorPluginInterface
{
Q_OBJECT
public:
GraphLayoutPlugin(QObject* parent, const QList< QVariant >&);
virtual ~GraphLayoutPlugin();
void showDialog(GraphDocumentPtr document) Q_DECL_OVERRIDE;
private:
const QScopedPointer<GraphLayoutPluginPrivate> d;
};
}
#endif
{
"Encoding": "UTF-8",
"KPlugin": {
"Category": "Plugins",
"Description": "This generates graph layouts automatically.",
"Description[pt_BR]": "Isto gera disposições do grafo automaticamente. ",
"Id": "rocs_graphlayoutplugin",
"License": "GPL",
"Name": "Graph Layout",
"Name[pt_BR]": "Disposição do Grafo",
"ServiceTypes": [
"rocs/editorplugins"
],
"Version": "0.1"
}
}
/*
* Copyright 2020 Dilson Almeida Guimarães <dilsonguim@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3, or any
* later version accepted by the membership of KDE e.V. (or its
* successor approved by the membership of KDE e.V.), which shall
* act as a proxy defined in Section 6 of version 3 of the license.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <https://www.gnu.org/licenses/>.
*/
#include "graphlayoutwidget.h"
#include "typenames.h"
#include "graphdocument.h"
#include "edge.h"
#include "modifiers/topology.h"
#include "logging_p.h"
#include <KLocalizedString>
#include <KComboBox>
#include <QtMath>
#include <QSlider>
#include <QList>
#include <QButtonGroup>
#include <QMessageBox>
using namespace GraphTheory;
GraphLayoutWidget::GraphLayoutWidget(GraphDocumentPtr document, QWidget *parent)
: QDialog(parent)
, m_document(document)
, m_seed(1)
, m_areaFactor(50)
, m_repellingForce(50)
, m_attractionForce(50)
{
setWindowTitle(i18nc("@title:window", "Graph Layout"));
QVBoxLayout *mainLayout = new QVBoxLayout(this);
setLayout(mainLayout);
QWidget *widget = new QWidget(this);
ui = new Ui::GraphLayoutWidget;
ui->setupUi(widget);
mainLayout->addWidget(widget);
connect(ui->mainButtons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui->mainButtons, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(this, &QDialog::accepted, this, &GraphLayoutWidget::layoutGraph);
connect(ui->areaFactorSlider, &QSlider::valueChanged, this, &GraphLayoutWidget::setAreaFactor);
connect(ui->repellingForceSlider, &QSlider::valueChanged, this, &GraphLayoutWidget::setRepellingForce);
connect(ui->attractionForceSlider, &QSlider::valueChanged, this, &GraphLayoutWidget::setAttractionForce);
// default values
ui->areaFactorSlider->setValue(50);
ui->repellingForceSlider->setValue(50);
ui->attractionForceSlider->setValue(50);
}
void GraphLayoutWidget::setAreaFactor(int areaFactor) {
m_areaFactor = areaFactor;
}
void GraphLayoutWidget::setRepellingForce(int repellingForce) {
m_repellingForce = repellingForce;
}
void GraphLayoutWidget::setAttractionForce(int attractionForce) {
m_attractionForce = attractionForce;
}
void GraphLayoutWidget::setSeed(int seed)
{
m_seed = seed;
}
void GraphLayoutWidget::layoutGraph()
{
//Sliders values map to parameteres with a non-linear scale
const qreal areaFactor = qPow(10, qreal(m_areaFactor - 50) / 50);
const qreal repellingForce = qPow(10, qreal(m_repellingForce - 50) / 50);
const qreal attractionForce = qPow(10, qreal(m_attractionForce - 50) / 50);
//Creates a small margin to make the layout look nicer.
const qreal margin = 5;
//Radius of each node. This should be equal to radius used when rendering the graph.
const qreal nodeRadius = 10;
const bool randomizeInitialPositions = true;
const quint32 seed = m_seed;
Topology::applyForceBasedLayout(m_document, nodeRadius, margin, areaFactor, repellingForce,
attractionForce, randomizeInitialPositions, seed);
close();
deleteLater();
}
GraphLayoutWidget::~GraphLayoutWidget()
{
delete ui;
}
/*
Copyright (C) 2020 Dilson Almeida Guimarães <dilsonguim@gmail.com>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GRAPHLAYOUTWIDGET_H
#define GRAPHLAYOUTWIDGET_H
#include "ui_graphlayoutwidget.h"
#include "typenames.h"
#include <QWidget>
#include <QDialog>
namespace GraphTheory {
class GraphLayoutWidget : public QDialog
{
Q_OBJECT
public:
explicit GraphLayoutWidget(GraphDocumentPtr document, QWidget *parent = 0);
~GraphLayoutWidget();
public slots:
/**
* Lay out the graph.
*/
void layoutGraph();
/**
* Updates the seed used for generating pseudo-random numbers.
*/
void setSeed(int seed);
/**
* Updates the area factor parameter of the layout algorithm.
*/
void setAreaFactor(int areaFactor);
/**
* Updates the repelling force parameter of the layout algorithm.
*/
void setRepellingForce(int repellingForce);
/**
* Updates the attraction force parameter of the layout algorithm.
*/
void setAttractionForce(int attractionForce);
private:
GraphDocumentPtr m_document;
int m_seed;
int m_areaFactor;
int m_repellingForce;
int m_attractionForce;
Ui::GraphLayoutWidget *ui;
};
}
#endif
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GraphLayoutWidget</class>
<widget class="QWidget" name="GraphLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>306</width>
<height>161</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>306</width>
<height>161</height>
</size>
</property>
<property name="windowTitle">
<string>Graph Layout</string>
</property>
<widget class="QDialogButtonBox" name="mainButtons">
<property name="geometry">
<rect>
<x>120</x>
<y>120</y>
<width>171</width>
<height>32</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>171</width>
<height>32</height>
</size>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QWidget" name="formLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>281</width>
<height>91</height>
</rect>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="horizontalSpacing">
<number>16</number>
</property>
<property name="verticalSpacing">
<number>16</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="areaFactorLabel">
<property name="text">
<string>Area factor:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSlider" name="areaFactorSlider">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="repellingForceLabel">
<property name="text">
<string>Repelling force:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="attractionForceLabel">
<property name="text">
<string>Attraction force:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSlider" name="repellingForceSlider">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSlider" name="attractionForceSlider">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>mainButtons</sender>
<signal>accepted()</signal>
<receiver>GraphLayoutWidget</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>mainButtons</sender>
<signal>rejected()</signal>
<receiver>GraphLayoutWidget</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
......@@ -21,5 +21,5 @@
#include "logging_p.h"
Q_LOGGING_CATEGORY(GRAPHTHEORY_FILEFORMAT, "org.kde.rocs.graphtheory.fileformat", QtWarningMsg)
Q_LOGGING_CATEGORY(GRAPHTHEORY_GENERAL, "org.kde.rocs.graphtheory.general", QtWarningMsg)
Q_LOGGING_CATEGORY(GRAPHTHEORY_GENERAL, "org.kde.rocs.graphtheory.general", QtDebugMsg)
Q_LOGGING_CATEGORY(GRAPHTHEORY_KERNEL, "org.kde.rocs.graphtheory.kernel", QtWarningMsg)
......@@ -26,6 +26,10 @@
#include <QList>
#include <QPair>
#include <QVector>
#include <QVector2D>
#include <QtMath>
#include <QPointF>
#include <QRandomGenerator>
#include <boost/graph/fruchterman_reingold.hpp>
#include <boost/graph/circle_layout.hpp>
......@@ -49,6 +53,14 @@ typedef boost::iterator_property_map < PositionVec::iterator,
typedef boost::graph_traits<Graph>::vertex_descriptor Vertex;
typedef QPair<int, int> BoostEdge;
typedef QPair<int, int> RemappedEdge;
struct RemappedGraph {
int numberOfNodes;
QMap<NodePtr, int> nodeToIndexMap;
QVector<RemappedEdge> edges;
};
// handle boost exceptions
namespace boost {
void throw_exception(std::exception const &e) {
......@@ -212,3 +224,290 @@ void Topology::undirectedGraphDefaultTopology(GraphDocumentPtr document)
applyCircleAlignment(document->nodes(), 300);
applyMinCutTreeAlignment(document->nodes());
}
/** @brief Calculates the size of a square in which a number of circles fit well.
*
* Given the number of circles and their radius, heuristically computes the length of the side
* of a square in which all circles can easily be placed without intersecting each other.
* Use this to figure out how big a square should be contain the drawing of graph well.
*
* By easily, understand the following:
* Consider a square with side length computed by this method. An algorithm that places circles
* at random positions inside this square, with uniform probability, should have a high chance
* of getting no intersections between the circles. For 3 circles or more, this chance is higher
* than 60%.
*