nf7/file/value_plot.cc

448 lines
12 KiB
C++

#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_config.hh"
#include "common/generic_memento.hh"
#include "common/generic_type_info.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/value.hh"
#include "common/yas_enum.hh"
namespace nf7 {
namespace {
class Plot final : public nf7::FileBase,
public nf7::GenericConfig,
public nf7::DirItem,
public nf7::Node {
public:
static inline const nf7::GenericTypeInfo<Plot> kType =
{"Value/Plot", {"nf7::DirItem"}, "data plotter"};
class Lambda;
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::string Stringify() const noexcept;
void Parse(const std::string&);
std::vector<Series> series;
};
Plot(nf7::Env& env, Data&& data = {}) noexcept :
nf7::FileBase(kType, env),
nf7::GenericConfig(mem_),
nf7::DirItem(nf7::DirItem::kMenu),
nf7::Node(nf7::Node::kNone),
life_(*this), log_(*this), win_(*this, "Plot"),
mem_(*this, std::move(data)) {
win_.onUpdate = [this]() { PlotGraph(); };
mem_.onRestore = mem_.onCommit = [this]() { BuildInputList(); };
Sanitize();
}
Plot(nf7::Deserializer& ar) : Plot(ar.env()) {
ar(win_, mem_->series);
Sanitize();
}
void Serialize(nf7::Serializer& ar) const noexcept override {
ar(win_, mem_->series);
}
std::unique_ptr<nf7::File> Clone(nf7::Env& env) const noexcept override {
return std::make_unique<Plot>(env, Data {mem_.data()});
}
std::shared_ptr<nf7::Node::Lambda> CreateLambda(
const std::shared_ptr<nf7::Node::Lambda>&) noexcept override;
nf7::Node::Meta GetMeta() const noexcept override {
return {inputs_, {}};
}
void UpdateMenu() noexcept override;
nf7::File::Interface* interface(const std::type_info& t) noexcept override {
return nf7::InterfaceSelector<
nf7::Config, 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_;
std::vector<std::string> inputs_;
// config management
void Sanitize() {
nf7::util::Uniq(mem_->series);
mem_.CommitAmend();
}
void BuildInputList() {
inputs_.clear();
inputs_.reserve(mem_->series.size());
for (const auto& s : mem_->series) {
inputs_.push_back(s.name);
}
}
// gui
void PlotGraph() noexcept;
};
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(const nf7::Node::Lambda::Msg& in) noexcept override
try {
f_.EnforceAlive();
const auto& series = f_->mem_->series;
auto itr = std::find(series.begin(), series.end(), in.name);
if (itr == series.end()) {
throw nf7::Exception {"unknown series name"};
}
const auto& s = *itr;
auto& v = in.value;
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&) {
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::UpdateMenu() noexcept {
win_.MenuItem();
}
void Plot::PlotGraph() 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 : mem_->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 st;
st << YAML::BeginMap;
st << YAML::Key << "series";
st << YAML::Value << YAML::BeginMap;
for (auto& s : series) {
st << YAML::Key << s.name;
st << YAML::Value << YAML::BeginMap;
st << YAML::Key << "type";
st << YAML::Value << std::string {magic_enum::enum_name(s.type)};
st << YAML::Key << "fmt" ;
st << YAML::Value << std::string {magic_enum::enum_name(s.fmt)};
st << YAML::EndMap;
}
st << YAML::EndMap;
st << YAML::EndMap;
return std::string {st.c_str(), st.size()};
}
void Plot::Data::Parse(const std::string& str)
try {
const auto& yaml = YAML::Load(str);
std::vector<Series> new_series;
for (auto& s : yaml["series"]) {
new_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());
}
series = std::move(new_series);
} 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