add Value/Plot

This commit is contained in:
falsycat 2022-09-19 10:05:01 +09:00
parent 8879e9ed41
commit 7dbda8d281
3 changed files with 502 additions and 0 deletions

View File

@ -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

View File

@ -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
View 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