Hi. I’m doing graph visualization. I made two classes. Edge Component - drawing and working with edges. GraphComponent - rendering and working with vertices. How do I define the hierarchy correctly or make one class object in another so that I can use these classes correctly?
#include <JuceHeader.h>
#include <iostream>
#include <vector>
#include <random>
class Graph_logics
{
std::vector<std::vector<int>> adjacencyMatrix;
public:
Graph_logics() { adjacencyMatrix = std::vector<std::vector<int>>(); }
void addVertex();
void removeVertex(int vertex);
void addEdge(int source, int destination, int weight);
void setEdgeWeight(int source, int destination, int weight);
void removeEdge(int source, int destination);
void printAdjacencyMatrix();
auto getAdjacencyMatrix();
};
void Graph_logics::addVertex()
{
int numVertices = adjacencyMatrix.size();
for (int i = 0; i < numVertices; i++) adjacencyMatrix[i].push_back(0);
adjacencyMatrix.push_back(std::vector<int>(numVertices + 1, 0));
}
void Graph_logics::removeVertex(int vertex)
{
int numVertices = adjacencyMatrix.size();
if (vertex >= numVertices) { return; }
adjacencyMatrix.erase(adjacencyMatrix.begin() + vertex);
for (auto& row : adjacencyMatrix) row.erase(row.begin() + vertex);
}
void Graph_logics::addEdge(int source, int destination, int weight)
{
int numVertices = adjacencyMatrix.size();
if (source >= numVertices || destination >= numVertices) { std::cout << "Invalid vertices!" << std::endl; return; }
adjacencyMatrix[source][destination] = weight;
}
void Graph_logics::setEdgeWeight(int source, int destination, int weight)
{
if (source >= 0 && source < adjacencyMatrix.size() &&
destination >= 0 && destination < adjacencyMatrix.size())
{
adjacencyMatrix[source][destination] = weight;
adjacencyMatrix[destination][source] = weight;
}
else {}
}
void Graph_logics::removeEdge(int source, int destination)
{
int numVertices = adjacencyMatrix.size();
if (source >= numVertices || destination >= numVertices) { std::cout << "Invalid vertices!" << std::endl; return; }
adjacencyMatrix[source][destination] = 0;
}
void Graph_logics::printAdjacencyMatrix()
{
int numVertices = adjacencyMatrix.size();
for (int i = 0; i < numVertices; i++)
{
for (int j = 0; j < numVertices; j++) std::cout << adjacencyMatrix[i][j] << " ";
std::cout << std::endl;
}
}
auto Graph_logics::getAdjacencyMatrix() { return adjacencyMatrix; }
class EdgeComponent : public juce::Component, public juce::Button::Listener
{
public:
EdgeComponent(Graph_logics& graph) : graph(graph)
{
addAndMakeVisible(addEdgeButton);
addEdgeButton.setButtonText("Add Edge");
addEdgeButton.addListener(this);
addAndMakeVisible(removeEdgeButton);
removeEdgeButton.setButtonText("Remove Edge");
removeEdgeButton.addListener(this);
}
EdgeComponent() : EdgeComponent(graph) {}
void paint(juce::Graphics& g) override
{
g.setColour(juce::Colours::black);
g.strokePath(edgePath, juce::PathStrokeType(2.0f));
g.setColour(juce::Colours::red);
g.fillPath(arrowPath);
}
void resized() override
{
const int buttonWidth = 100;
const int buttonHeight = 30;
const int buttonMargin = 10;
addEdgeButton.setBounds(buttonMargin, getHeight() - buttonHeight - buttonMargin, buttonWidth, buttonHeight);
removeEdgeButton.setBounds(buttonMargin + buttonWidth + buttonMargin, getHeight() - buttonHeight - buttonMargin, buttonWidth, buttonHeight);
}
void buttonClicked(juce::Button* button) override
{
if (button == &addEdgeButton)
{
if (!isAddingEdge)
{
isAddingEdge = true;
addEdgeButton.setButtonText("Cancel");
removeEdgeButton.setEnabled(false);
}
else
{
isAddingEdge = false;
addEdgeButton.setButtonText("Add Edge");
removeEdgeButton.setEnabled(true);
}
}
else if (button == &removeEdgeButton)
{
if (!isRemovingEdge)
{
isRemovingEdge = true;
removeEdgeButton.setButtonText("Cancel");
addEdgeButton.setEnabled(false);
}
else
{
isRemovingEdge = false;
removeEdgeButton.setButtonText("Remove Edge");
addEdgeButton.setEnabled(true);
}
}
}
void mouseUp(const juce::MouseEvent& event) override
{
if (isAddingEdge)
{
const juce::Point<int> mousePos = event.getPosition();
const int numVertices = graph.getAdjacencyMatrix().size();
const int radius = 100;
const juce::Point<int> center(getWidth() / 2, getHeight() / 2);
const float angleStep = 2 * juce::MathConstants<float>::pi / numVertices;
for (int i = 0; i < numVertices; ++i)
{
const float angle = i * angleStep;
const int x = static_cast<int>(center.x + radius * std::cos(angle));
const int y = static_cast<int>(center.y + radius * std::sin(angle));
juce::Rectangle<int> vertexBounds(x - vertexRadius, y - vertexRadius, vertexRadius * 2, vertexRadius * 2);
if (vertexBounds.contains(mousePos))
{
if (selectedVertex == -1)
{
selectedVertex = i;
}
else
{
graph.addEdge(selectedVertex, i, 1);
selectedVertex = -1;
}
break;
}
}
repaint();
}
}
private:
Graph_logics& graph;
juce::Path edgePath;
juce::Path arrowPath;
juce::TextButton addEdgeButton;
juce::TextButton removeEdgeButton;
bool isAddingEdge = false;
bool isRemovingEdge = false;
int selectedVertex = -1;
const int vertexRadius = 20;
void updatePaths()
{
edgePath.clear();
arrowPath.clear();
const auto& adjacencyMatrix = graph.getAdjacencyMatrix();
const int numVertices = adjacencyMatrix.size();
const int radius = 100;
const juce::Point<int> center(getWidth() / 2, getHeight() / 2);
const float angleStep = 2 * juce::MathConstants<float>::pi / numVertices;
for (int i = 0; i < numVertices; ++i)
{
const float angle = i * angleStep;
const int x = static_cast<int>(center.x + radius * std::cos(angle));
const int y = static_cast<int>(center.y + radius * std::sin(angle));
for (int j = 0; j < numVertices; ++j)
{
if (adjacencyMatrix[i][j] != 0)
{
const int destX = static_cast<int>(center.x + radius * std::cos(j * angleStep));
const int destY = static_cast<int>(center.y + radius * std::sin(j * angleStep));
if (isRemovingEdge && selectedVertex == i && j == getHoveredVertex())
{
edgePath.startNewSubPath(x, y);
edgePath.lineTo(destX, destY);
}
else
{
const float arrowSize = 10.0f;
const float dx = destX - x;
const float dy = destY - y;
const float length = std::sqrt(dx * dx + dy * dy);
const float arrowAngle = std::atan2(dy, dx);
const float arrowX = destX - (length > vertexRadius ? arrowSize * dx / length : 0.0f);
const float arrowY = destY - (length > vertexRadius ? arrowSize * dy / length : 0.0f);
if (adjacencyMatrix[j][i] != 0)
{
edgePath.startNewSubPath(x, y);
edgePath.lineTo(destX, destY);
arrowPath.addArrow(juce::Line<float>(arrowX, arrowY, destX, destY), arrowSize, arrowSize, 0.4f);
}
else
{
edgePath.startNewSubPath(x, y);
edgePath.cubicTo(x + 100, y, destX - 100, destY, destX, destY);
arrowPath.addArrow(juce::Line<float>(arrowX, arrowY, destX, destY), arrowSize, arrowSize, 0.4f);
}
}
}
}
}
}
int getHoveredVertex() const
{
const juce::Point<int> mousePos = getMouseXYRelative();
const int numVertices = graph.getAdjacencyMatrix().size();
const int radius = 100;
const juce::Point<int> center(getWidth() / 2, getHeight() / 2);
const float angleStep = 2 * juce::MathConstants<float>::pi / numVertices;
for (int i = 0; i < numVertices; ++i)
{
const float angle = i * angleStep;
const int x = static_cast<int>(center.x + radius * std::cos(angle));
const int y = static_cast<int>(center.y + radius * std::sin(angle));
juce::Rectangle<int> vertexBounds(x - vertexRadius, y - vertexRadius, vertexRadius * 2, vertexRadius * 2);
if (vertexBounds.contains(mousePos))
return i;
}
return -1;
}
void mouseExit(const juce::MouseEvent& event) override
{
repaint();
}
void mouseEnter(const juce::MouseEvent& event) override
{
repaint();
}
};
class GraphComponent : public juce::Component, public juce::Button::Listener
{
public:
GraphComponent()
{
addAndMakeVisible(addButton);
addButton.setButtonText("Add Vertex");
addButton.addListener(this);
addButton.setColour(juce::TextButton::buttonColourId, juce::Colours::green);
addAndMakeVisible(removeButton);
removeButton.setButtonText("Remove Vertex");
removeButton.addListener(this);
removeButton.setColour(juce::TextButton::buttonColourId, juce::Colours::red);
addAndMakeVisible(edgeComponent);
}
void paint(juce::Graphics& g) override
{
g.fillAll(juce::Colours::white);
const int radius = 100;
const juce::Point<int> center(getWidth() / 2, getHeight() / 2);
juce::Font font(20, juce::Font::bold);
g.setFont(font);
const int numVertices = graph.getAdjacencyMatrix().size();
const float angleStep = 2 * juce::MathConstants<float>::pi / numVertices;
for (int i = 0; i < numVertices; ++i)
{
const float angle = i * angleStep;
const int x = static_cast<int>(center.x + radius * std::cos(angle)) - vertexSize / 2;
const int y = static_cast<int>(center.y + radius * std::sin(angle)) - vertexSize / 2;
juce::Rectangle<int> vertexBounds(x, y, vertexSize, vertexSize);
g.setColour(vertexColors[i % vertexColors.size()]);
g.fillEllipse(vertexBounds.toFloat());
g.setColour(juce::Colours::black);
g.drawEllipse(vertexBounds.toFloat(), 1);
g.drawText(juce::String(i + 1), vertexBounds, juce::Justification::centred);
}
}
void resized() override
{
const int buttonWidth = 100;
const int buttonHeight = 30;
const int buttonMargin = 10;
addButton.setBounds(buttonMargin, buttonMargin, buttonWidth, buttonHeight);
removeButton.setBounds(buttonMargin, addButton.getBottom() + buttonMargin, buttonWidth, buttonHeight);
}
void buttonClicked(juce::Button* button) override
{
if (button == &addButton)
{
if (graph.getAdjacencyMatrix().size() < maxVertices)
{
graph.addVertex();
repaint();
}
}
else if (button == &removeButton)
{
if (!isRemovingVertex)
{
isRemovingVertex = true;
removeButton.setButtonText("Cancel");
}
else
{
isRemovingVertex = false;
removeButton.setButtonText("Remove Vertex");
}
}
}
void mouseUp(const juce::MouseEvent& event) override
{
if (isRemovingVertex)
{
const juce::Point<int> mousePos = event.getPosition();
const int numVertices = graph.getAdjacencyMatrix().size();
const int radius = 100;
const juce::Point<int> center(getWidth() / 2, getHeight() / 2);
const float angleStep = 2 * juce::MathConstants<float>::pi / numVertices;
for (int i = 0; i < numVertices; ++i)
{
const float angle = i * angleStep;
const int x = static_cast<int>(center.x + radius * std::cos(angle)) - vertexSize / 2;
const int y = static_cast<int>(center.y + radius * std::sin(angle)) - vertexSize / 2;
juce::Rectangle<int> vertexBounds(x, y, vertexSize, vertexSize);
if (vertexBounds.contains(mousePos))
{
graph.removeVertex(i);
repaint();
break;
}
}
isRemovingVertex = false;
removeButton.setButtonText("Remove Vertex");
}
}
private:
static const int vertexSize = 40;
static const int maxVertices = 6;
const std::vector<juce::Colour> vertexColors = {
juce::Colours::blue,
juce::Colours::red,
juce::Colours::green,
juce::Colours::orange,
juce::Colours::purple,
juce::Colours::yellow
};
EdgeComponent edgeComponent;
Graph_logics graph;
juce::TextButton addButton;
juce::TextButton removeButton;
bool isRemovingVertex = false;
};
class MainComponent : public juce::Component
{
public:
MainComponent()
{
setSize(600, 400);
addAndMakeVisible(graphComponent);
}
void paint(juce::Graphics& g) override
{
}
void resized() override
{
graphComponent.setBounds(getLocalBounds());
}
private:
Graph_logics graph;
GraphComponent graphComponent;
};
class GraphApplication : public juce::JUCEApplication
{
public:
GraphApplication() {}
void initialise(const juce::String& commandLine) override
{
mainWindow.reset(new MainWindow(getApplicationName()));
mainWindow->setContentOwned(new MainComponent(), true);
mainWindow->centreWithSize(800, 600);
mainWindow->setVisible(true);
}
void shutdown() override { mainWindow = nullptr; }
const juce::String getApplicationName() override { return "Graph Visualization"; }
const juce::String getApplicationVersion() override { return "1.0.0"; }
bool moreThanOneInstanceAllowed() override { return true; }
void systemRequestedQuit() override { quit(); }
void anotherInstanceStarted(const juce::String& commandLine) override {}
class MainWindow : public juce::DocumentWindow
{
public:
MainWindow(juce::String name)
: DocumentWindow(name,
juce::Desktop::getInstance().getDefaultLookAndFeel()
.findColour(juce::ResizableWindow::backgroundColourId),
DocumentWindow::closeButton)
{
setUsingNativeTitleBar(true);
setContentOwned(new MainComponent(), true);
#if JUCE_IOS || JUCE_ANDROID
setFullScreen(true);
#else
setResizable(false, false);
centreWithSize(getWidth(), getHeight());
#endif
setVisible(true);
}
void closeButtonPressed() override { JUCEApplication::getInstance()->systemRequestedQuit(); }
private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow)
};
private: std::unique_ptr<MainWindow> mainWindow;
};
START_JUCE_APPLICATION(GraphApplication)