Using classes

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)