This commit is contained in:
2024-09-12 23:12:19 +04:00
committed by Andrey Alekseev
commit 73aec484df
6 changed files with 621 additions and 0 deletions

405
src/MyApplication.cpp Normal file
View File

@@ -0,0 +1,405 @@
#include "MyApplication.h"
#include <Wt/Chart/WAbstractChartModel.h>
#include <cfloat>
#include <ranges>
#include <algorithm>
class SinModel : public Chart::WAbstractChartModel
{
public:
SinModel(vector<Sin>& data, int rows)
: Chart::WAbstractChartModel{}
, sinuses{data}
, rows_{rows}
{ }
virtual double data(int row, int column) const override {
double x = -M_PI + row * 2*M_PI / (rowCount() - 1);
switch ( column ) {
case 0: return x;
case 1:
return ranges::fold_left_first(
sinuses | views::transform([x](Sin s){return s.get(x);}),
std::plus<double>()
).value_or(0);
default:
try { return sinuses.at(column-2).get(x); }
catch (out_of_range e){ return 0; }
}
}
virtual int columnCount() const override { return 2 + sinuses.size(); };
virtual int rowCount() const override { return rows_; }
void setRows(int rows) {
rows_ = rows;
changed().emit();
}
private:
int rows_;
vector<Sin>& sinuses;
};
class FourierModel : public Chart::WAbstractChartModel
{
public:
FourierModel(shared_ptr<SinModel> dm)
: Chart::WAbstractChartModel{}
, discreteModel{dm}
{
dm->changed().connect([this](){
data_.clear();
data_.reserve(rowCount());
vector<double> vals;
vals.reserve( discreteModel->rowCount());
double first_positive = DBL_MAX;
for(int i = 0; i < discreteModel->rowCount(); i++) {
auto v = discreteModel->data(i, 1);
vals.push_back(v);
if ( abs(vals[i]) > 0 && abs(vals[i]) < first_positive )
first_positive = abs(vals[i]);
}
for ( int k = 0; k < rowCount(); k++ ) {
if ( first_positive > 0 ) {
double x = 0,
y = 0;
for ( int n = 0; n < discreteModel->rowCount(); n++ ) {
double e = 2*M_PI*k*n/discreteModel->rowCount();
double x = vals[n] / first_positive;
x+=x*cos(e);
y-=x*sin(e);
}
data_[k] = {sqrt(x*x+y*y), atan2(y,x)};
} else data_[k] = {0,0};
}
changed().emit();
});
}
virtual double data(int row, int column) const override {
//double x = row/(2*M_PI);
switch ( column ) {
case 0: return row;
case 1: return data_[row].first;
case 2: return data_[row].second;
default: return 0;
}
}
virtual int columnCount() const override { return 3; };
virtual int rowCount() const override { return discreteModel->rowCount()/2; }
private:
shared_ptr<SinModel> discreteModel;
vector<pair<double, double>> data_;
};
#include <Wt/WBootstrap5Theme.h>
#include <Wt/Chart/WAbstractChartModel.h>
#include <Wt/Chart/WAxisSliderWidget.h>
#include <Wt/Chart/WCartesianChart.h>
#include <Wt/Chart/WDataSeries.h>
#include <Wt/WApplication.h>
#include <Wt/WContainerWidget.h>
#include <Wt/WObject.h>
#include <Wt/WShadow.h>
#include <Wt/WSpinBox.h>
#include <Wt/WDoubleSpinBox.h>
#include <Wt/WLabel.h>
#include <Wt/WPushButton.h>
#include <Wt/WColorPicker.h>
#include <Wt/WCheckBox.h>
#include <Wt/WStandardItemModel.h>
MyApplication::MyApplication(const WEnvironment& env)
: WApplication{env}
{
setTheme(make_shared<WBootstrap5Theme>());
styleSheet().addRule("html, body", "height: 100%");
root()->addStyleClass("d-flex h-100");
auto left = root()->addNew<WContainerWidget>();
left->addStyleClass("d-flex flex-column h-100");
{ // график
auto chartContainer = left->addNew<WContainerWidget>();
chart = chartContainer->addNew<Chart::WCartesianChart>();
//chart->setBackground(WColor(220, 220, 220));
chart->setType(Chart::ChartType::Scatter);
roughModel = make_shared<SinModel>(sinuses, 100);
auto roughSeries = make_unique<Chart::WDataSeries>(1, Chart::SeriesType::Line);
auto roughSeries_ = roughSeries.get();
roughSeries_->setModel(roughModel);
roughSeries_->setXSeriesColumn(0);
roughSeries->setHidden(true);
chart->addSeries(std::move(roughSeries));
detailedModel = make_shared<SinModel>(sinuses, 10000);
auto seriesPtr = make_unique<Chart::WDataSeries>(1, Chart::SeriesType::Line);
auto series = seriesPtr.get();
series->setModel(detailedModel);
series->setXSeriesColumn(0);
series->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
chart->addSeries(std::move(seriesPtr));
chart->setFollowCurve(series);
discreteModel = make_shared<SinModel>(sinuses, 0);
auto pointsDSPtr = make_unique<Chart::WDataSeries>(1, Chart::SeriesType::Point);
pointsDS = pointsDSPtr.get();
pointsDS->setModel(discreteModel);
pointsDS->setXSeriesColumn(0);
pointsDS->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
pointsDS->setHidden(true);
pointsDS->setLabelsEnabled(Chart::Axis::Y);
pointsDS->setLabelColor(StandardColor::Black);
chart->addSeries(std::move(pointsDSPtr));
chart->axis(Chart::Axis::X).setMaximumZoomRange( 2*M_PI+1 );
chart->axis(Chart::Axis::X).setMinimumZoomRange( M_PI / 16.0 );
chart->axis(Chart::Axis::X).setMinimum( -M_PI );
chart->axis(Chart::Axis::X).setMaximum( M_PI );
chart->axis(Chart::Axis::Y).setMinimumZoomRange(0.1);
chart->axis(Chart::Axis::X).setLocation(Chart::AxisValue::Zero);
chart->axis(Chart::Axis::Y).setLocation(Chart::AxisValue::Zero);
chart->axis(Chart::Axis::X).setGridLinesEnabled(true);
chart->axis(Chart::Axis::Y).setGridLinesEnabled(true);
chart->resize(800, 600);
chart->setPanEnabled(true);
chart->setZoomEnabled(true);
chart->setCrosshairEnabled(true);
chart->setOnDemandLoadingEnabled(true);
auto sliderWidget = chartContainer->addNew<Chart::WAxisSliderWidget>(roughSeries_);
sliderWidget->resize(800, 80);
sliderWidget->setSelectionAreaPadding(40, Side::Left | Side::Right);
}
auto controlsContainer = left->addNew<WContainerWidget>();
controlsContainer->addStyleClass("h-100 d-flex flex-column");
controlsContainer->setOverflow(Overflow::Hidden);
{ // функции
auto form = controlsContainer->addNew<WContainerWidget>();
form->addStyleClass("d-flex flex-row align-items-center gap-1 p-3");
list = controlsContainer->addNew<WContainerWidget>();
list->addStyleClass("d-flex flex-column gap-1 p-2");
list->setOverflow(Overflow::Auto);
showAll = form->addNew<WCheckBox>();
showAll->setTristate();
showAll->clicked().connect([=,this](){
for ( auto s : sinuses ) {
((WCheckBox*)s.el->children().at(0))
->setChecked(showAll->isChecked());
s.ds->setHidden(!showAll->isChecked());
}
checkCount = showAll->isChecked() ? sinuses.size() : 0;
chart->update();
});
form->addNew<WText>("a=");
auto a = form->addNew<WDoubleSpinBox>();
form->addNew<WText>("w=");
auto w = form->addNew<WDoubleSpinBox>();
form->addNew<WText>("f=");
auto f = form->addNew<WDoubleSpinBox>();
for ( auto i : {a,w,f} ) {
i->setWidth(100);
i->setRange(-1000, 1000);
}
auto addBtn = form->addNew<WPushButton>("+");
addBtn->clicked().connect([=,this](){
addSin({a->value(), w->value(), f->value()});
});
}
auto right = root()->addNew<WContainerWidget>();
right->addStyleClass("d-flex flex-column h-100 gap-1");
right->setOverflow(Overflow::Hidden, Orientation::Horizontal);
{ // дискретизация
auto form = right->addNew<WContainerWidget>();
form->addStyleClass("d-flex flex-row gap-3");
auto show = form->addNew<WCheckBox>("дискретизация");
auto freq = form->addNew<WSpinBox>();
freq->setWidth(120);
freq->setRange(0, 1000);
show->clicked().connect([=,this](){
pointsDS->setHidden(!show->isChecked());
chart->update();
});
freq->changed().connect([=,this](){
discreteModel->setRows(freq->value());
});
discreteModel->changed().connect([this](){
vals->clear();
for ( int i = 0; i < discreteModel->rowCount(); i++ ) {
auto x = discreteModel->data(i,1);
vals->addNew<WText>(format("{:.2f}", x))
->addStyleClass("border rounded-3 p-1");
}
});
}
vals = right->addNew<WContainerWidget>();
vals->setOverflow(Overflow::Auto, Orientation::Horizontal);
vals->addStyleClass("d-flex gap-1 p-1 text-nowrap");
fourierChart = right->addNew<Chart::WCartesianChart>();
fourierChart->setPlotAreaPadding(100);
fourierChart->setType(Chart::ChartType::Scatter);
fourierModel = make_shared<FourierModel>(discreteModel);
fourierChart->setModel(static_pointer_cast<Chart::WAbstractChartModel>(fourierModel));
auto seriesPtr = make_unique<Chart::WDataSeries>(2, Chart::SeriesType::Point);
auto phaseSeries = seriesPtr.get();
phaseSeries->setModel(fourierChart->model());
phaseSeries->setXSeriesColumn(0);
phaseSeries->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
fourierChart->addSeries(std::move(seriesPtr));
seriesPtr = make_unique<Chart::WDataSeries>(1, Chart::SeriesType::Line);
auto ampSeries = seriesPtr.get();
ampSeries->setModel(fourierChart->model());
ampSeries->setXSeriesColumn(0);
ampSeries->setShadow(WShadow(3, 3, WColor(0, 0, 0, 127), 3));
fourierChart->addSeries(std::move(seriesPtr));
auto id = fourierChart->addYAxis(make_unique<Chart::WAxis>());
ampSeries->bindToYAxis(id);
fourierChart->setFollowCurve(ampSeries);
fourierChart->yAxis(0).setTitle("φ");
fourierChart->yAxis(0).setTextPen(phaseSeries->pen());
fourierChart->yAxis(id).setTitle("α");
fourierChart->yAxis(id).setTextPen(ampSeries->pen());
fourierChart->axis(Chart::Axis::X).setLabelAngle(-90);
fourierChart->setPanEnabled(true);
fourierChart->setZoomEnabled(true);
fourierChart->setCrosshairEnabled(true);
fourierChart->resize(800, 600);
}
void MyApplication::addSin(Sin s) {
if ( find(sinuses.cbegin(), sinuses.cend(), s) != sinuses.cend() ) return;
sinuses.push_back(s);
int index = sinuses.size()-1;
roughModel->changed().emit();
detailedModel->changed().emit();
discreteModel->changed().emit();
auto seriesPtr = make_unique<Chart::WDataSeries>(index+2, Chart::SeriesType::Line);
auto series = seriesPtr.get();
series->setModel(detailedModel);
series->setXSeriesColumn(0);
series->setShadow({3, 3, WColor{0, 0, 0, 127}, 3});
series->setPen({StandardColor::Green});
chart->addSeries(std::move(seriesPtr));
sinuses[index].ds = series;
series->setHidden(true);
auto el = list->addNew<WContainerWidget>();
{
el->addStyleClass("border rounded-3 p-3 d-flex justify-content-between align-items-center gap-3");
auto show = el->addNew<WCheckBox>();
show->clicked().connect([=,this](){
sinuses.at(series->modelColumn()-2).ds->setHidden(!show->isChecked());
chart->update();
checkCount+= show->isChecked() ? 1 : -1;
showAll->setCheckState(
checkCount == 0 ? CheckState::Unchecked
: checkCount < sinuses.size() ? CheckState::PartiallyChecked
: CheckState::Checked
);
});
auto c = el->addNew<WColorPicker>(WColor{StandardColor::Black});
c->setColor(StandardColor::Green);
c->changed().connect([=,this](){
sinuses.at(series->modelColumn()-2).ds->setPen(WPen{c->color()});
chart->update();
});
c->setWidth(50);
el->addNew<WText>(WString{"{1} cos( {2}t + {3} )"}.arg(s.a).arg(s.w).arg(s.f));
auto del = el->addNew<WPushButton>("x");
del->setStyleClass("btn-danger");
del->clicked().connect([=,this](){
removeSin(series->modelColumn()-2);
el->removeFromParent();
});
}
sinuses[index].el = el;
int freq = 2*M_PI / ranges::max_element(sinuses, [](Sin a, Sin b){return abs(a.w) > abs(b.w);})->w * 4;
if ( detailedModel->rowCount() < freq ) detailedModel->setRows(freq);
else detailedModel->setRows(10000);
showAll->setCheckState(
checkCount == 0 ? CheckState::Unchecked
: checkCount < sinuses.size() ? CheckState::PartiallyChecked
: CheckState::Checked
);
}
void MyApplication::removeSin(int index) {
auto& s = sinuses.at(index);
chart->removeSeries(s.ds);
sinuses.erase(next(sinuses.begin(), index));
for ( ; index < sinuses.size(); index++ )
sinuses[index].ds->setModelColumn(index+2);
roughModel->changed().emit();
detailedModel->changed().emit();
discreteModel->changed().emit();
showAll->setCheckState(
checkCount == 0 ? CheckState::Unchecked
: checkCount < sinuses.size() ? CheckState::PartiallyChecked
: CheckState::Checked
);
}

60
src/MyApplication.h Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
#include <Wt/WApplication.h>
#include <cmath>
using namespace Wt;
using namespace std;
struct sin {
double a;
double w;
double f;
Chart::WDataSeries* ds;
WContainerWidget* el;
double get(double t) const
{ return a*cos(w*t+f); }
bool operator ==(const sin& other) const {
return a == other.a && w == other.w && f == other.f;
}
bool operator <(const sin& other) const {
return (a+w+f) < (other.a + other.w + other.f);
}
};
typedef struct sin Sin;
class SinModel;
class FourierModel;
class MyApplication : public WApplication
{
public:
MyApplication(const WEnvironment& env);
private:
Chart::WCartesianChart* chart;
shared_ptr<SinModel> roughModel;
shared_ptr<SinModel> detailedModel;
shared_ptr<SinModel> discreteModel;
Chart::WDataSeries* pointsDS;
Chart::WCartesianChart* fourierChart;
shared_ptr<FourierModel> fourierModel;
WContainerWidget* list;
WCheckBox* showAll;
size_t checkCount = 0;
WContainerWidget* vals;
vector<Sin> sinuses;
void addSin(Sin s);
void removeSin(int index);
};

28
src/main.cpp Normal file
View File

@@ -0,0 +1,28 @@
#include <Wt/WServer.h>
#include "MyApplication.h"
using namespace std;
using namespace Wt;
int main(int argc, char *argv[]) {
int code = 0;
try {
WServer server(argc, argv, WTHTTP_CONFIGURATION);
using namespace placeholders;
server.addEntryPoint( EntryPointType::Application,
[](const WEnvironment& env){return make_unique<MyApplication>(env);},
"/"
);
server.run();
}
catch ( WServer::Exception& e ) { cerr << e.what() << endl; code = -1; }
catch ( std::exception& e ) { cerr << "exception: " << e.what() << endl; code = -1; }
return code;
}