Files
ts-qt/components/charts/line_chart.cc

222 lines
6.1 KiB
C++

//
// Created by Codex on 2025/12/10.
//
#include "line_chart.hh"
#include <QLinearGradient>
#include <algorithm>
#include <cmath>
#include <limits>
using creeper::line_widget::internal::LinePlot;
LinePlot::LinePlot() {
setBackground(Qt::transparent);
}
LinePlot::~LinePlot() = default;
void LinePlot::load_theme_manager(creeper::ThemeManager& mgr) {
mgr.append_handler(this, [this](const creeper::ThemeManager& manager) {
scheme_ = manager.color_scheme();
apply_theme();
if (initialized_) {
replot();
}
});
}
void LinePlot::set_data(const QVector<QPointF>& points) {
points_ = points;
trim_points();
if (initialized_) {
update_graph();
}
}
void LinePlot::set_max_points(int count) {
const int clamped = std::max(1, count);
if (clamped == max_points_) {
return;
}
max_points_ = clamped;
trim_points();
if (initialized_) {
update_graph();
}
}
void LinePlot::paintEvent(QPaintEvent* event) {
if (!initialized_) {
initialize_plot();
}
QCustomPlot::paintEvent(event);
}
void LinePlot::initialize_plot() {
if (initialized_) {
return;
}
axisRect()->setAutoMargins(QCP::msNone);
axisRect()->setMargins(QMargins(40, 12, 12, 18));
axisRect()->setBackground(QBrush(QColor(246, 249, 255)));
legend->setVisible(false);
xAxis->setVisible(true);
yAxis->setVisible(true);
xAxis->setRange(0.0, std::max(10, max_points_));
yAxis->setRange(0.0, default_y_range_);
xAxis->grid()->setVisible(false);
yAxis->grid()->setVisible(false);
xAxis->setTicks(false);
yAxis->setTicks(true);
xAxis->setSubTicks(false);
yAxis->setSubTicks(false);
xAxis->setTickLength(0);
yAxis->setTickLength(0);
yAxis->setTickLabelPadding(6);
yAxis->setLabelPadding(8);
graph_ = addGraph();
graph_->setLineStyle(QCPGraph::lsLine);
graph_->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::NoPen), QBrush(Qt::white), 6));
graph_->setBrush(QColor(16, 54, 128, 42));
graph_->setAntialiased(true);
graph_->setAdaptiveSampling(true);
initialized_ = true;
apply_theme();
update_graph();
}
void LinePlot::reset_graph_range() {
xAxis->setRange(0.0, std::max(10, max_points_));
yAxis->setRange(0.0, default_y_range_);
}
void LinePlot::apply_theme() {
QColor line_color{ 16, 54, 128 };
QColor text_color{ 30, 30, 30 };
QColor bg_color{ 232, 238, 248 };
if (scheme_.has_value()) {
if (scheme_->primary.isValid()) {
line_color = scheme_->primary;
}
if (scheme_->on_surface.isValid()) {
text_color = scheme_->on_surface;
}
if (scheme_->surface_container_high.isValid()) {
bg_color = scheme_->surface_container_high;
}
else if (scheme_->surface_container.isValid()) {
bg_color = scheme_->surface_container;
}
else if (scheme_->surface.isValid()) {
bg_color = scheme_->surface;
}
}
QColor grid_color = bg_color;
QPen axis_pen(bg_color);
axis_pen.setWidthF(1.0);
xAxis->setBasePen(axis_pen);
yAxis->setBasePen(axis_pen);
xAxis->setTickPen(axis_pen);
yAxis->setTickPen(axis_pen);
xAxis->setTickLabelColor(Qt::transparent);
yAxis->setTickLabelColor(text_color);
xAxis->setLabelColor(Qt::transparent);
yAxis->setLabelColor(text_color);
axisRect()->setBackground(bg_color);
if (graph_) {
QPen pen(line_color);
pen.setWidthF(3.0);
pen.setCapStyle(Qt::RoundCap);
graph_->setPen(pen);
QLinearGradient fill_grad(0, 0, 0, 1);
fill_grad.setCoordinateMode(QGradient::CoordinateMode::ObjectBoundingMode);
fill_grad.setColorAt(0.0, QColor(line_color.red(), line_color.green(), line_color.blue(), 70));
fill_grad.setColorAt(1.0, QColor(line_color.red(), line_color.green(), line_color.blue(), 18));
graph_->setBrush(QBrush(fill_grad));
auto scatter = graph_->scatterStyle();
scatter.setPen(QPen(line_color, 1.5));
scatter.setBrush(QBrush(QColor(bg_color).lighter(104)));
scatter.setSize(7);
graph_->setScatterStyle(scatter);
}
}
void LinePlot::update_graph() {
if (!initialized_) {
return;
}
if (!graph_) {
graph_ = addGraph();
apply_theme();
}
if (points_.isEmpty()) {
graph_->data()->clear();
reset_graph_range();
replot();
return;
}
QVector<double> keys(points_.size());
QVector<double> values(points_.size());
double min_key = std::numeric_limits<double>::max();
double max_key = std::numeric_limits<double>::lowest();
double min_val = std::numeric_limits<double>::max();
double max_val = std::numeric_limits<double>::lowest();
for (int i = 0; i < points_.size(); ++i) {
const auto& pt = points_[i];
keys[i] = pt.x();
values[i] = pt.y();
min_key = std::min(min_key, pt.x());
max_key = std::max(max_key, pt.x());
min_val = std::min(min_val, pt.y());
max_val = std::max(max_val, pt.y());
}
graph_->setData(keys, values, true);
if (min_key == std::numeric_limits<double>::max()) {
reset_graph_range();
}
else {
const double key_span = std::max(1e-3, max_key - min_key);
xAxis->setRange(min_key, max_key + key_span * 0.02);
double value_span = max_val - min_val;
if (value_span < 1e-3) {
value_span = std::max(std::abs(max_val), 1.0);
min_val = max_val - value_span * 0.5;
}
const double padding = std::max(value_span * 0.25, 1.0);
yAxis->setRange(min_val - padding, max_val + padding);
}
replot();
}
void LinePlot::trim_points() {
if (max_points_ <= 0 || points_.size() <= max_points_) {
return;
}
const int start = points_.size() - max_points_;
points_ = points_.mid(start);
}
using namespace creeper;
void SumLinePlot::paintEvent(QPaintEvent* event) {
line_widget::internal::LinePlot::paintEvent(event);
}