QT C++ Practical: Implementation of a Draggable Chart Component System

1. System Architecture Design

1. Architecture Flowchart

QT C++ Practical: Implementation of a Draggable Chart Component System

2. Class Diagram Design

QT C++ Practical: Implementation of a Draggable Chart Component System

2. Core Code Implementation

1. Chart Component Interface (IComponent)

// ichartcomponent.h
#ifndef ICHARTCOMPONENT_H
#define ICHARTCOMPONENT_H

#include<QWidget>
#include<QVariantMap>
#include<QPaintEvent>

class IComponent : public QWidget {
    Q_OBJECT
public:
explicit IComponent(QWidget* parent = nullptr) : QWidget(parent) {}
virtual ~IComponent() = default;

// Basic property settings
virtual void setType(const QString& type) = 0;
virtual QString getType() const = 0;

// Data interaction
virtual void setData(const QVariantMap& data) = 0;
virtual QVariantMap getData() const = 0;

// Drawing control
virtual void updateChart() = 0;  // Trigger redraw

// Parameter editing
virtual QDialog* createParameterDialog(QWidget* parent = nullptr) = 0;

signals:
void chartUpdated();  // Chart update notification
};

#endif // ICHARTCOMPONENT_H

2. Specific Chart Component Implementation (Example: Line Chart)

Line Chart Component
// linechart.h
#ifndef LINECHART_H
#define LINECHART_H

#include "ichartcomponent.h"
#include<QVector>
#include<QColor>

class LineChart : public IComponent {
    Q_OBJECT
public:
explicit LineChart(QWidget* parent = nullptr);

// IComponent interface implementation
void setType(const QString& type) override { m_type = type; }
QString getType() const override { return m_type; }
void setData(const QVariantMap& data) override;
QVariantMap getData() const override;
void updateChart() override { update(); }
QDialog* createParameterDialog(QWidget* parent = nullptr) override;

protected:
void paintEvent(QPaintEvent* event) override;

private:
    QString m_type = "LineChart";
    QVector<QPointF> m_dataPoints;  // Data points (x,y)
    QColor m_lineColor = Qt::blue;
    QColor m_bgColor = QColor(240, 240, 240);
    QFont m_axisFont = QFont("微软雅黑", 9);
};

// linechart.cpp
#include "linechart.h"
#include<QPainter>
#include<QLinearGradient>
#include<QDialog>
#include<QVBoxLayout>
#include<QFormLayout>
#include<QColorDialog>
#include<QSpinBox>

void LineChart::setData(const QVariantMap& data) {
    m_dataPoints.clear();
// Example data format: {"x":[1,2,3,4], "y":[10,20,15,25]}
if (data.contains("x") && data.contains("y")) {
        QVector<double> x = data["x"].value<QVector<double>>();
        QVector<double> y = data["y"].value<QVector<double>>();
for (int i = 0; i < x.size(); ++i) {
            m_dataPoints.append(QPointF(x[i], y[i]));
        }
    }
update();
}

QVariantMap LineChart::getData() const {
return {
        {"x", QVector<double>() << 1 << 2 << 3 << 4},  // Example return
        {"y", QVector<double>() << 10 << 20 << 15 << 25}
    };
}

void LineChart::paintEvent(QPaintEvent* event) {
Q_UNUSED(event);
QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

// Draw background
    painter.fillRect(rect(), m_bgColor);

// Calculate drawing area (leave margins)
int margin = 30;
    QRectF plotRect = rect().adjusted(margin, margin, -margin, -margin);

// Draw axes
    painter.setPen(QPen(Qt::black, 1));
    painter.drawLine(plotRect.topLeft(), plotRect.bottomRight());  // Diagonal axis example
    painter.drawLine(plotRect.topLeft(), plotRect.topRight());    // Horizontal axis
    painter.drawLine(plotRect.topLeft(), plotRect.bottomLeft());  // Vertical axis

// Draw line
    painter.setPen(QPen(m_lineColor, 2));
    QPainterPath path;
    QPointF startPoint = plotRect.topLeft() + QPointF(
        (m_dataPoints.first().x() - plotRect.left()) / plotRect.width() * plotRect.width(),
        plotRect.height() - (m_dataPoints.first().y() - plotRect.top()) / plotRect.height() * plotRect.height()
    );
    path.moveTo(startPoint);
for (const auto& p : m_dataPoints) {
        QPointF point = plotRect.topLeft() + QPointF(
            (p.x() - plotRect.left()) / plotRect.width() * plotRect.width(),
            plotRect.height() - (p.y() - plotRect.top()) / plotRect.height() * plotRect.height()
        );
        path.lineTo(point);
    }
    painter.drawPath(path);
}

QDialog* LineChart::createParameterDialog(QWidget* parent) {
    QDialog* dialog = new QDialog(parent);
    dialog->setWindowTitle("Line Chart Parameter Settings");

    QVBoxLayout* layout = new QVBoxLayout(dialog);
    QFormLayout* form = new QFormLayout;

// Color selection
    QPushButton* colorBtn = new QPushButton("Select Line Color");
    QObject::connect(colorBtn, &QPushButton::clicked, [this]() {
        m_lineColor = QColorDialog::getColor(m_lineColor, this);
update();
    });
    form->addRow("Line Color:", colorBtn);

// Background color selection
    QPushButton* bgColorBtn = new QPushButton("Select Background Color");
    QObject::connect(bgColorBtn, &QPushButton::clicked, [this]() {
        m_bgColor = QColorDialog::getColor(m_bgColor, this);
update();
    });
    form->addRow("Background Color:", bgColorBtn);

    layout->addLayout(form);
    layout->addStretch();
    dialog->setLayout(layout);
return dialog;
}

3. Chart Factory and Plugin System

Chart Factory
// chartfactory.h
#ifndef CHARTFACTORY_H
#define CHARTFACTORY_H

#include "ichartcomponent.h"
#include<QMap>
#include<QString>

class ChartFactory {
public:
static ChartFactory& instance() {
static ChartFactory factory;
return factory;
    }

void registerChart(const QString& type, std::function<IComponent*()> creator) {
        m_creators[type] = creator;
    }

IComponent* createChart(const QString& type) {
if (m_creators.contains(type)) {
return m_creators[type]();
        }
return nullptr;
    }

private:
ChartFactory() = default;
    QMap<QString, std::function<IComponent*()>> m_creators;
};

// Registration macro (for automatic plugin registration)
#define REGISTER_CHART(type, cls) \
    namespace { \
        struct cls##Register { \
            cls##Register() { \
                ChartFactory::instance().registerChart(#type, [](){ return new cls(); }); \
            } \
        } cls##Registrar; \
    }
Plugin Interface and Singleton Manager
// ichartplugin.h
#ifndef ICHARTPLUGIN_H
#define ICHARTPLUGIN_H

#include "ichartcomponent.h"
#include<QString>

class IChartPlugin {
public:
virtual ~IChartPlugin() = default;
virtual QString pluginName() const = 0;
virtual IComponent* createComponent() = 0;
virtual void destroyComponent(IComponent* comp) = 0;
};

// Plugin Manager (singleton)
class PluginManager {
public:
static PluginManager& instance() {
static PluginManager manager;
return manager;
    }

void loadPlugins(const QString& path) {
// Dynamically load plugin libraries (example pseudocode)
// QPluginLoader loader(path);
// if (loader.load()) {
//     IChartPlugin* plugin = qobject_cast<IChartPlugin*>(loader.instance());
//     if (plugin) m_plugins.append(plugin);
// }
    }

IComponent* createChart(const QString& pluginName) {
for (auto plugin : m_plugins) {
if (plugin->pluginName() == pluginName) {
return plugin->createComponent();
            }
        }
return nullptr;
    }

private:
PluginManager() = default;
    QList<IChartPlugin*> m_plugins;
};

4. Drag-and-Drop Implementation

Component Area (Draggable List)
// componentpanel.h
#ifndef COMPONENTPANEL_H
#define COMPONENTPANEL_H

#include<QWidget>
#include<QListWidget>
#include<QMimeData>
#include<QDragEnterEvent>
#include<QDropEvent>

class ComponentPanel : public QWidget {
    Q_OBJECT
public:
explicit ComponentPanel(QWidget* parent = nullptr) : QWidget(parent) {
        QVBoxLayout* layout = new QVBoxLayout(this);
        mListWidget = new QListWidget;
        layout->addWidget(mListWidget);

// Register supported chart types
        QStringList types = {"LineChart", "PieChart", "BarChart", "HeatMap", "RadarChart"};
for (const auto& type : types) {
            mListWidget->addItem(type);
        }
    }

protected:
void dragEnterEvent(QDragEnterEvent* event) override {
if (event->mimeData()->hasText()) {
            event->acceptProposedAction();
        }
    }

void dragMoveEvent(QDragMoveEvent* event) override {
if (event->mimeData()->hasText()) {
            event->acceptProposedAction();
        }
    }

void dropEvent(QDropEvent* event) override {
const QString type = event->mimeData()->text();
if (!type.isEmpty()) {
emit chartDragged(type);  // Send chart type to runtime area
            event->acceptProposedAction();
        }
    }

signals:
void chartDragged(const QString& type);

private:
    QListWidget* mListWidget;
};
Runtime Area (Receive Drag-and-Drop and Display Chart)
// runtimepanel.h
#ifndef RUNTIMEPANEL_H
#define RUNTIMEPANEL_H

#include<QWidget>
#include<QVBoxLayout>
#include<QMap>
#include "ichartcomponent.h"
#include "componentpanel.h"

class RuntimePanel : public QWidget {
    Q_OBJECT
public:
explicit RuntimePanel(QWidget* parent = nullptr) : QWidget(parent) {
        QVBoxLayout* layout = new QVBoxLayout(this);
setAcceptDrops(true);

// Connect component area drag signal
        ComponentPanel* compPanel = new ComponentPanel(this);
        layout->addWidget(compPanel);
connect(compPanel, &ComponentPanel::chartDragged, this, &RuntimePanel::onChartDragged);
    }

protected:
void dragEnterEvent(QDragEnterEvent* event) override {
if (event->mimeData()->hasText()) {
            event->acceptProposedAction();
        }
    }

void dropEvent(QDropEvent* event) override {
const QString type = event->mimeData()->text();
if (!type.isEmpty()) {
createChart(type);
            event->acceptProposedAction();
        }
    }

private slots:
void onChartDragged(const QString& type) {
createChart(type);
    }

private:
void createChart(const QString& type) {
        IComponent* chart = ChartFactory::instance().createChart(type);
if (chart) {
            chart->setData(generateSampleData());  // Set sample data
            m_charts.append(chart);
layout()->addWidget(chart);
        }
    }

QVariantMap generateSampleData() {
// Generate sample data (adjust based on chart type)
return {
            {"x", QVector<double>() << 1 << 2 << 3 << 4},
            {"y", QVector<double>() << 10 << 20 << 15 << 25}
        };
    }

QVBoxLayout* layout() { return qobject_cast<QVBoxLayout*>(this->layout()); }
    QList<IComponent*> m_charts;
};

5. Right-Click Edit Function Implementation

// Add right-click menu support in IComponent subclasses
// Example: LineChart
void LineChart::contextMenuEvent(QContextMenuEvent* event) {
    QDialog* dialog = createParameterDialog(this);
if (dialog->exec() == QDialog::Accepted) {
updateChart();  // Refresh chart after applying parameters
    }
    dialog->deleteLater();
}

// Forward right-click events in runtime area components
void RuntimePanel::contextMenuEvent(QContextMenuEvent* event) {
    QWidget* child = childAt(event->pos());
if (IComponent* chart = qobject_cast<IComponent*>(child)) {
        chart->contextMenuEvent(event);  // Forward right-click event to chart component
    }
}

3. Plugin Development Example (Hot Plug Implementation)

1. Plugin Interface Implementation (Example: Pie Chart Plugin)

// piechartplugin.h
#ifndef PIECHARTPLUGIN_H
#define PIECHARTPLUGIN_H

#include "ichartplugin.h"
#include "piechart.h"

class PieChartPlugin : public IChartPlugin {
public:
QString pluginName() const override { return "PieChart"; }
IComponent* createComponent() override { return new PieChart(); }
void destroyComponent(IComponent* comp) override { delete comp; }
};

// Export plugin (Windows use __declspec(dllexport))
#if defined(PieChartPlugin_EXPORTS)
#define PIECHARTPLUGIN_API __declspec(dllexport)
#else
#define PIECHARTPLUGIN_API __declspec(dllimport)
#endif

extern "C" PIECHARTPLUGIN_API IChartPlugin* createPlugin() {
return new PieChartPlugin();
}

4. System Deployment and Usage

  1. Compile Plugins: Compile each chart plugin into a dynamic library (.dll/.so) and place it in the specified plugin directory.

  2. Load Plugins: The main program loads all plugins at startup using <span>PluginManager::instance().loadPlugins("plugins/")</span>.

  3. Drag-and-Drop Operation: Drag chart types from the component area to the runtime area to automatically generate the corresponding chart.

  4. Parameter Editing: Right-click on the chart in the runtime area to open the parameter dialog, modify it, and click “OK” to update the chart.

Leave a Comment