add Value/Plot
This commit is contained in:
parent
8879e9ed41
commit
7dbda8d281
@ -146,6 +146,7 @@ target_sources(nf7
|
||||
file/system_logger.cc
|
||||
file/system_native_file.cc
|
||||
file/value_curve.cc
|
||||
file/value_plot.cc
|
||||
)
|
||||
target_link_libraries(nf7
|
||||
PRIVATE
|
||||
|
@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <string>
|
||||
@ -10,6 +12,18 @@
|
||||
|
||||
namespace nf7::util {
|
||||
|
||||
inline std::string_view Trim(
|
||||
std::string_view str,
|
||||
const std::function<bool(char)>& func = [](auto c) { return std::isspace(c); }) noexcept {
|
||||
while (!str.empty() && func(str.front())) {
|
||||
str.remove_prefix(1);
|
||||
}
|
||||
while (!str.empty() && func(str.back())) {
|
||||
str.remove_suffix(1);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
inline std::optional<std::string_view> IterateTerms(std::string_view str, char c, size_t& i) noexcept {
|
||||
std::string_view ret;
|
||||
while (ret.empty() && i < str.size()) {
|
||||
|
487
file/value_plot.cc
Normal file
487
file/value_plot.cc
Normal file
@ -0,0 +1,487 @@
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <typeinfo>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <implot.h>
|
||||
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <yas/serialize.hpp>
|
||||
#include <yas/types/utility/usertype.hpp>
|
||||
|
||||
#include "nf7.hh"
|
||||
|
||||
#include "common/dir_item.hh"
|
||||
#include "common/file_base.hh"
|
||||
#include "common/generic_memento.hh"
|
||||
#include "common/generic_type_info.hh"
|
||||
#include "common/gui_node.hh"
|
||||
#include "common/gui_popup.hh"
|
||||
#include "common/gui_window.hh"
|
||||
#include "common/life.hh"
|
||||
#include "common/logger_ref.hh"
|
||||
#include "common/node.hh"
|
||||
#include "common/ptr_selector.hh"
|
||||
#include "common/util_algorithm.hh"
|
||||
#include "common/util_string.hh"
|
||||
#include "common/value.hh"
|
||||
#include "common/yas_enum.hh"
|
||||
|
||||
|
||||
namespace nf7 {
|
||||
namespace {
|
||||
|
||||
class Plot final : public nf7::FileBase,
|
||||
public nf7::DirItem,
|
||||
public nf7::Node {
|
||||
public:
|
||||
static inline const nf7::GenericTypeInfo<Plot> kType =
|
||||
{"Value/Plot", {"nf7::DirItem", "nf7::Node"}};
|
||||
static void UpdateTypeTooltip() noexcept {
|
||||
ImGui::TextUnformatted("plotter");
|
||||
ImGui::Bullet(); ImGui::TextUnformatted("implements nf7::Node");
|
||||
}
|
||||
|
||||
enum SeriesType {
|
||||
kLine,
|
||||
kScatter,
|
||||
kBars,
|
||||
};
|
||||
enum SeriesFormat {
|
||||
kU8 = 0x11,
|
||||
kS8 = 0x21,
|
||||
kU16 = 0x12,
|
||||
kS16 = 0x22,
|
||||
kU32 = 0x14,
|
||||
kS32 = 0x24,
|
||||
kF32 = 0x34,
|
||||
kF64 = 0x38,
|
||||
};
|
||||
struct SeriesData {
|
||||
SeriesFormat fmt;
|
||||
|
||||
nf7::Value::ConstVector xs;
|
||||
nf7::Value::ConstVector ys;
|
||||
|
||||
double param[3];
|
||||
size_t count = 0;
|
||||
size_t offset = 0;
|
||||
size_t stride = 0;
|
||||
|
||||
int flags;
|
||||
};
|
||||
struct Series {
|
||||
std::string name;
|
||||
SeriesType type;
|
||||
SeriesFormat fmt;
|
||||
|
||||
std::shared_ptr<SeriesData> data;
|
||||
|
||||
Series(std::string_view n = "", SeriesType t = kLine, SeriesFormat f = kF32) noexcept :
|
||||
name(n), type(t), fmt(f), data(std::make_shared<SeriesData>()) {
|
||||
}
|
||||
Series(const Series&) = default;
|
||||
Series(Series&&) = default;
|
||||
Series& operator=(const Series&) = default;
|
||||
Series& operator=(Series&&) = default;
|
||||
|
||||
bool operator==(std::string_view v) const noexcept { return name == v; }
|
||||
bool operator==(const Series& s) const noexcept { return name == s.name; }
|
||||
void serialize(auto& ar) {
|
||||
ar(name, type, fmt);
|
||||
}
|
||||
|
||||
void Update() const noexcept;
|
||||
};
|
||||
struct Data {
|
||||
std::vector<Series> series;
|
||||
|
||||
std::string Stringify() const noexcept;
|
||||
static Data Parse(const std::string&);
|
||||
};
|
||||
|
||||
class Lambda;
|
||||
|
||||
Plot(nf7::Env& env, const nf7::gui::Window* win = nullptr, Data&& data = {}) noexcept :
|
||||
nf7::FileBase(kType, env, {&log_, &config_popup_}),
|
||||
nf7::DirItem(nf7::DirItem::kMenu | nf7::DirItem::kWidget),
|
||||
nf7::Node(nf7::Node::kNone),
|
||||
life_(*this), log_(*this), win_(*this, "Plot", win), mem_(std::move(data)),
|
||||
config_popup_("Value/Plot: config") {
|
||||
mem_.onRestore = mem_.onCommit = [this]() { BuildInputList(); };
|
||||
|
||||
config_popup_.onOpen = [this]() { return this->data().Stringify(); };
|
||||
config_popup_.onApply = [this](auto& str) {
|
||||
this->data() = Data::Parse(str);
|
||||
mem_.Commit();
|
||||
};
|
||||
|
||||
Sanitize();
|
||||
}
|
||||
|
||||
Plot(nf7::Deserializer& ar) : Plot(ar.env()) {
|
||||
ar(win_, data().series);
|
||||
Sanitize();
|
||||
}
|
||||
void Serialize(nf7::Serializer& ar) const noexcept override {
|
||||
ar(win_, data().series);
|
||||
}
|
||||
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
|
||||
return std::make_unique<Plot>(env, &win_, Data {data()});
|
||||
}
|
||||
|
||||
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
|
||||
|
||||
std::span<const std::string> GetInputs() const noexcept override {
|
||||
return inputs_;
|
||||
}
|
||||
std::span<const std::string> GetOutputs() const noexcept override {
|
||||
return {};
|
||||
}
|
||||
|
||||
void Update() noexcept override;
|
||||
void UpdateWidget() noexcept override;
|
||||
void UpdateMenu() noexcept override;
|
||||
|
||||
void UpdatePlot() noexcept;
|
||||
|
||||
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
|
||||
return InterfaceSelector<
|
||||
nf7::DirItem, nf7::Memento, nf7::Node>(t).Select(this, &mem_);
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<Plot> life_;
|
||||
nf7::LoggerRef log_;
|
||||
|
||||
nf7::gui::Window win_;
|
||||
|
||||
nf7::GenericMemento<Data> mem_;
|
||||
Data& data() noexcept { return mem_.data(); }
|
||||
const Data& data() const noexcept { return mem_.data(); }
|
||||
|
||||
std::vector<std::string> inputs_;
|
||||
|
||||
nf7::gui::ConfigPopup config_popup_;
|
||||
|
||||
|
||||
void Sanitize() {
|
||||
nf7::util::Uniq(data().series);
|
||||
mem_.CommitAmend();
|
||||
}
|
||||
void BuildInputList() {
|
||||
inputs_.clear();
|
||||
inputs_.reserve(data().series.size());
|
||||
for (const auto& s : data().series) {
|
||||
inputs_.push_back(s.name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class Plot::Lambda final : public nf7::Node::Lambda {
|
||||
public:
|
||||
Lambda(Plot& f, const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept :
|
||||
nf7::Node::Lambda(f, parent), f_(f.life_) {
|
||||
}
|
||||
|
||||
void Handle(std::string_view k, const nf7::Value& v,
|
||||
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override
|
||||
try {
|
||||
f_.EnforceAlive();
|
||||
|
||||
const auto& series = f_->data().series;
|
||||
auto itr = std::find(series.begin(), series.end(), k);
|
||||
if (itr == series.end()) {
|
||||
throw nf7::Exception {"unknown series name"};
|
||||
}
|
||||
const auto& s = *itr;
|
||||
|
||||
auto& data = *s.data;
|
||||
if (v.isVector()) {
|
||||
const auto& vec = v.vector();
|
||||
const auto fmtsz = static_cast<size_t>(s.fmt & 0xF);
|
||||
data = SeriesData {
|
||||
.fmt = s.fmt,
|
||||
.xs = vec,
|
||||
.ys = nullptr,
|
||||
.param = {0},
|
||||
.count = vec->size() / fmtsz,
|
||||
.offset = 0,
|
||||
.stride = fmtsz,
|
||||
.flags = 0,
|
||||
};
|
||||
switch (s.type) {
|
||||
case kLine:
|
||||
case kScatter:
|
||||
data.param[0] = 1; // xscale
|
||||
break;
|
||||
case kBars:
|
||||
data.param[0] = 0.67; // barsize
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (v.isTuple()) {
|
||||
// TODO: parameters
|
||||
|
||||
} else {
|
||||
throw nf7::Exception {"expected vector"};
|
||||
}
|
||||
} catch (nf7::ExpiredException&) {
|
||||
} catch (nf7::Exception& e) {
|
||||
f_->log_.Warn("plotter error");
|
||||
}
|
||||
|
||||
private:
|
||||
nf7::Life<Plot>::Ref f_;
|
||||
};
|
||||
std::shared_ptr<nf7::Node::Lambda> Plot::CreateLambda(
|
||||
const std::shared_ptr<nf7::Node::Lambda>& parent) noexcept {
|
||||
return std::make_shared<Plot::Lambda>(*this, parent);
|
||||
}
|
||||
|
||||
|
||||
void Plot::Update() noexcept {
|
||||
nf7::FileBase::Update();
|
||||
|
||||
if (win_.Begin()) {
|
||||
UpdatePlot();
|
||||
}
|
||||
win_.End();
|
||||
}
|
||||
void Plot::UpdateWidget() noexcept {
|
||||
if (ImGui::Button("plot window")) {
|
||||
win_.SetFocus();
|
||||
}
|
||||
if (ImGui::Button("config")) {
|
||||
config_popup_.Open();
|
||||
}
|
||||
|
||||
config_popup_.Update();
|
||||
}
|
||||
void Plot::UpdateMenu() noexcept {
|
||||
ImGui::MenuItem("plot window", nullptr, &win_.shown());
|
||||
|
||||
if (ImGui::MenuItem("config")) {
|
||||
config_popup_.Open();
|
||||
}
|
||||
}
|
||||
|
||||
void Plot::UpdatePlot() noexcept {
|
||||
if (ImPlot::BeginPlot("##plot", ImGui::GetContentRegionAvail())) {
|
||||
ImPlot::SetupAxis(ImAxis_X1, "X", ImPlotAxisFlags_AutoFit);
|
||||
ImPlot::SetupAxis(ImAxis_Y1, "Y", ImPlotAxisFlags_AutoFit);
|
||||
for (const auto& s : data().series) {
|
||||
s.Update();
|
||||
}
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Plot::Series::Update() const noexcept {
|
||||
switch (type) {
|
||||
case kLine: {
|
||||
const auto Line = [&]<typename T>() {
|
||||
if (data->xs && data->ys) {
|
||||
ImPlot::PlotLine(
|
||||
name.c_str(),
|
||||
reinterpret_cast<const T*>(data->xs->data()),
|
||||
reinterpret_cast<const T*>(data->ys->data()),
|
||||
static_cast<int>(data->count),
|
||||
data->flags,
|
||||
static_cast<int>(data->offset),
|
||||
static_cast<int>(data->stride));
|
||||
} else if (data->xs) {
|
||||
ImPlot::PlotLine(
|
||||
name.c_str(),
|
||||
reinterpret_cast<const T*>(data->xs->data()),
|
||||
static_cast<int>(data->count),
|
||||
data->param[0],
|
||||
data->param[1],
|
||||
data->flags,
|
||||
static_cast<int>(data->offset),
|
||||
static_cast<int>(data->stride));
|
||||
}
|
||||
};
|
||||
switch (data->fmt) {
|
||||
case kU8: Line.operator()<uint8_t>(); break;
|
||||
case kS8: Line.operator()<int8_t>(); break;
|
||||
case kU16: Line.operator()<uint16_t>(); break;
|
||||
case kS16: Line.operator()<int16_t>(); break;
|
||||
case kU32: Line.operator()<uint32_t>(); break;
|
||||
case kS32: Line.operator()<int32_t>(); break;
|
||||
case kF32: Line.operator()<float>(); break;
|
||||
case kF64: Line.operator()<double>(); break;
|
||||
}
|
||||
} break;
|
||||
case kScatter: {
|
||||
const auto Scatter = [&]<typename T>() {
|
||||
if (data->xs && data->ys) {
|
||||
ImPlot::PlotScatter(
|
||||
name.c_str(),
|
||||
reinterpret_cast<const T*>(data->xs->data()),
|
||||
reinterpret_cast<const T*>(data->ys->data()),
|
||||
static_cast<int>(data->count),
|
||||
data->flags,
|
||||
static_cast<int>(data->offset),
|
||||
static_cast<int>(data->stride));
|
||||
} else if (data->xs) {
|
||||
ImPlot::PlotScatter(
|
||||
name.c_str(),
|
||||
reinterpret_cast<const T*>(data->xs->data()),
|
||||
static_cast<int>(data->count),
|
||||
data->param[0],
|
||||
data->param[1],
|
||||
data->flags,
|
||||
static_cast<int>(data->offset),
|
||||
static_cast<int>(data->stride));
|
||||
}
|
||||
};
|
||||
switch (data->fmt) {
|
||||
case kU8: Scatter.operator()<uint8_t>(); break;
|
||||
case kS8: Scatter.operator()<int8_t>(); break;
|
||||
case kU16: Scatter.operator()<uint16_t>(); break;
|
||||
case kS16: Scatter.operator()<int16_t>(); break;
|
||||
case kU32: Scatter.operator()<uint32_t>(); break;
|
||||
case kS32: Scatter.operator()<int32_t>(); break;
|
||||
case kF32: Scatter.operator()<float>(); break;
|
||||
case kF64: Scatter.operator()<double>(); break;
|
||||
}
|
||||
} break;
|
||||
case kBars: {
|
||||
const auto Bars = [&]<typename T>() {
|
||||
if (data->xs && data->ys) {
|
||||
ImPlot::PlotBars(
|
||||
name.c_str(),
|
||||
reinterpret_cast<const T*>(data->xs->data()),
|
||||
reinterpret_cast<const T*>(data->ys->data()),
|
||||
static_cast<int>(data->count),
|
||||
data->param[0],
|
||||
data->flags,
|
||||
static_cast<int>(data->offset),
|
||||
static_cast<int>(data->stride));
|
||||
} else if (data->xs) {
|
||||
ImPlot::PlotBars(
|
||||
name.c_str(),
|
||||
reinterpret_cast<const T*>(data->xs->data()),
|
||||
static_cast<int>(data->count),
|
||||
data->param[0],
|
||||
data->param[1],
|
||||
data->flags,
|
||||
static_cast<int>(data->offset),
|
||||
static_cast<int>(data->stride));
|
||||
}
|
||||
};
|
||||
switch (data->fmt) {
|
||||
case kU8: Bars.operator()<uint8_t>(); break;
|
||||
case kS8: Bars.operator()<int8_t>(); break;
|
||||
case kU16: Bars.operator()<uint16_t>(); break;
|
||||
case kS16: Bars.operator()<int16_t>(); break;
|
||||
case kU32: Bars.operator()<uint32_t>(); break;
|
||||
case kS32: Bars.operator()<int32_t>(); break;
|
||||
case kF32: Bars.operator()<float>(); break;
|
||||
case kF64: Bars.operator()<double>(); break;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
std::string Plot::Data::Stringify() const noexcept {
|
||||
YAML::Emitter Y;
|
||||
Y << YAML::BeginMap;
|
||||
Y << YAML::Key << "series";
|
||||
Y << YAML::Value << YAML::BeginMap;
|
||||
for (auto& s : series) {
|
||||
Y << YAML::Key << s.name;
|
||||
Y << YAML::Value << YAML::BeginMap;
|
||||
Y << YAML::Key << "type";
|
||||
Y << YAML::Value << std::string {magic_enum::enum_name(s.type)};
|
||||
Y << YAML::Key << "fmt" ;
|
||||
Y << YAML::Value << std::string {magic_enum::enum_name(s.fmt)};
|
||||
Y << YAML::EndMap;
|
||||
}
|
||||
Y << YAML::EndMap;
|
||||
Y << YAML::EndMap;
|
||||
return std::string {Y.c_str(), Y.size()};
|
||||
}
|
||||
Plot::Data Plot::Data::Parse(const std::string& str)
|
||||
try {
|
||||
const auto& root = YAML::Load(str);
|
||||
|
||||
Data d;
|
||||
for (auto& s : root["series"]) {
|
||||
d.series.emplace_back(
|
||||
s.first.as<std::string>(),
|
||||
magic_enum::enum_cast<SeriesType>(s.second["type"].as<std::string>()).value(),
|
||||
magic_enum::enum_cast<SeriesFormat>(s.second["fmt"].as<std::string>()).value());
|
||||
}
|
||||
return d;
|
||||
} catch (std::bad_optional_access&) {
|
||||
throw nf7::Exception {"unknown enum"};
|
||||
} catch (YAML::Exception& e) {
|
||||
throw nf7::Exception {e.what()};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace nf7
|
||||
|
||||
|
||||
namespace magic_enum::customize {
|
||||
|
||||
template <>
|
||||
constexpr customize_t magic_enum::customize::enum_name<nf7::Plot::SeriesType>(nf7::Plot::SeriesType v) noexcept {
|
||||
switch (v) {
|
||||
case nf7::Plot::SeriesType::kLine: return "line";
|
||||
case nf7::Plot::SeriesType::kScatter: return "scatter";
|
||||
case nf7::Plot::SeriesType::kBars: return "bars";
|
||||
}
|
||||
return invalid_tag;
|
||||
}
|
||||
template <>
|
||||
constexpr customize_t magic_enum::customize::enum_name<nf7::Plot::SeriesFormat>(nf7::Plot::SeriesFormat v) noexcept {
|
||||
switch (v) {
|
||||
case nf7::Plot::SeriesFormat::kU8: return "u8";
|
||||
case nf7::Plot::SeriesFormat::kS8: return "s8";
|
||||
case nf7::Plot::SeriesFormat::kU16: return "u16";
|
||||
case nf7::Plot::SeriesFormat::kS16: return "s16";
|
||||
case nf7::Plot::SeriesFormat::kU32: return "u32";
|
||||
case nf7::Plot::SeriesFormat::kS32: return "s32";
|
||||
case nf7::Plot::SeriesFormat::kF32: return "f32";
|
||||
case nf7::Plot::SeriesFormat::kF64: return "f64";
|
||||
}
|
||||
return invalid_tag;
|
||||
}
|
||||
|
||||
} // namespace magic_enum::customize
|
||||
|
||||
|
||||
namespace yas::detail {
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
yas::detail::type_prop::is_enum,
|
||||
yas::detail::ser_case::use_internal_serializer,
|
||||
F, nf7::Plot::SeriesType> :
|
||||
nf7::EnumSerializer<nf7::Plot::SeriesType> {
|
||||
};
|
||||
|
||||
template <size_t F>
|
||||
struct serializer<
|
||||
yas::detail::type_prop::is_enum,
|
||||
yas::detail::ser_case::use_internal_serializer,
|
||||
F, nf7::Plot::SeriesFormat> :
|
||||
nf7::EnumSerializer<nf7::Plot::SeriesFormat> {
|
||||
};
|
||||
|
||||
} // namespace yas::detail
|
Loading…
x
Reference in New Issue
Block a user