Skip to content

Commit 58ba41c

Browse files
committed
feat: add multi-expression plotting with color selection etc
1 parent 72b0a8d commit 58ba41c

File tree

1 file changed

+89
-100
lines changed

1 file changed

+89
-100
lines changed

src/core/Core/Application.cpp

Lines changed: 89 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
#include "Application.hpp"
2-
32
#include <SDL2/SDL.h>
43
#include <backends/imgui_impl_sdl2.h>
54
#include <backends/imgui_impl_sdlrenderer2.h>
65
#include <imgui.h>
7-
86
#include <memory>
97
#include <string>
108
#include <vector>
11-
129
#include "Core/DPIHandler.hpp"
1310
#include "Core/Debug/Instrumentor.hpp"
1411
#include "Core/Log.hpp"
@@ -18,6 +15,8 @@
1815
#include "exprtk.hpp"
1916
#include "funcs.hpp"
2017

18+
using namespace std;
19+
2120
namespace App {
2221

2322
Application::Application(const std::string& title) {
@@ -112,15 +111,43 @@ ExitStatus App::Application::run() {
112111
const ImVec2 base_pos = viewport->Pos;
113112
const ImVec2 base_size = viewport->Size;
114113

115-
static char function[1024] = "tanh(x)";
116114
static float zoom = 100.0f;
115+
struct GraphExpr {
116+
char expr[1024];
117+
ImVec4 col;
118+
bool on;
119+
};
120+
static vector<GraphExpr> exprs = {{"sin(x)", ImVec4(0.0f, 0.5f, 1.0f, 1.0f), true}};
117121

118122
// Left Pane (expression)
119123
{
120124
ImGui::SetNextWindowPos(base_pos);
121125
ImGui::SetNextWindowSize(ImVec2(base_size.x * 0.25f, base_size.y));
122126
ImGui::Begin("Left Pane", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
123-
ImGui::InputTextMultiline("##search", function, sizeof(function), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 4));
127+
ImGui::Text("Expressions:");
128+
ImGui::Separator();
129+
for (int i = 0; i < exprs.size(); i++) {
130+
ImGui::PushID(i);
131+
ImGui::InputTextMultiline(("##expr" + to_string(i)).c_str(), exprs[i].expr, sizeof(exprs[i].expr), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 2));
132+
ImGui::SameLine();
133+
ImGui::ColorEdit3(("##color" + to_string(i)).c_str(), (float*)&exprs[i].col, ImGuiColorEditFlags_NoInputs);
134+
ImGui::Checkbox(("##enabled" + to_string(i)).c_str(), &exprs[i].on);
135+
ImGui::SameLine();
136+
ImGui::Text("Enabled");
137+
if (exprs.size() > 1) {
138+
ImGui::SameLine();
139+
if (ImGui::Button("Delete")) {
140+
exprs.erase(exprs.begin() + i);
141+
i--;
142+
}
143+
}
144+
ImGui::Separator();
145+
ImGui::PopID();
146+
}
147+
if (ImGui::Button("Add Expression")) {
148+
exprs.push_back({"x^2", ImVec4(1.0f, 0.5f, 0.0f, 1.0f), true});
149+
}
150+
ImGui::Separator();
124151
ImGui::SliderFloat("Graph Scale", &zoom, 10.0f, 500.0f, "%.1f");
125152
ImGui::End();
126153
}
@@ -139,106 +166,68 @@ ExitStatus App::Application::run() {
139166
float lineThickness = 6.0f;
140167
draw_list->AddLine(ImVec2(canvas_p0.x, origin.y), ImVec2(canvas_p1.x, origin.y), IM_COL32(0, 0, 0, 255), lineThickness);
141168
draw_list->AddLine(ImVec2(origin.x, canvas_p0.y), ImVec2(origin.x, canvas_p1.y), IM_COL32(0, 0, 0, 255), lineThickness);
142-
std::vector<ImVec2> points;
143-
144-
// (f(t), g(t))
145-
std::string func_str(function);
146-
147-
148-
bool plotted = false;
149-
150-
if (!func_str.empty() && func_str.front() == '(' && func_str.back() == ')') {
151-
const std::string inner = func_str.substr(1, func_str.size() - 2);
152-
// top-level comma separating f and g
153-
int depth = 0;
154-
size_t split_pos = std::string::npos;
155-
for (size_t i = 0; i < inner.size(); ++i) {
156-
char c = inner[i];
157-
if (c == '(')
158-
++depth;
159-
else if (c == ')')
160-
--depth;
161-
else if (c == ',' && depth == 0) {
162-
split_pos = i;
163-
break;
164-
}
165-
}
166169

167-
if (split_pos != std::string::npos) {
168-
std::string fx = trim(inner.substr(0, split_pos));
169-
std::string gx = trim(inner.substr(split_pos + 1));
170-
171-
// Prepare exprtk
172-
double t = 0.0;
173-
exprtk::symbol_table<double> sym_t;
174-
sym_t.add_constants();
175-
addConstants(sym_t);
176-
sym_t.add_variable("t", t);
177-
178-
exprtk::expression<double> expr_fx;
179-
expr_fx.register_symbol_table(sym_t);
180-
exprtk::expression<double> expr_gx;
181-
expr_gx.register_symbol_table(sym_t);
182-
183-
exprtk::parser<double> parser;
184-
bool ok_fx = parser.compile(fx, expr_fx);
185-
bool ok_gx = parser.compile(gx, expr_gx);
186-
187-
if (ok_fx && ok_gx) {
188-
// iterate t
189-
const double t_min = -10.0;
190-
const double t_max = 10.0;
191-
const double t_step = 0.02;
192-
193-
for (t = t_min; t <= t_max; t += t_step) {
194-
const double vx = expr_fx.value();
195-
const double vy = expr_gx.value();
196-
197-
198-
ImVec2 screen_pos(origin.x + static_cast<float>(vx * zoom),
199-
origin.y - static_cast<float>(vy * zoom));
200-
points.push_back(screen_pos);
170+
for (const auto& e : exprs) {
171+
if (!e.on) continue;
172+
vector<ImVec2> pts;
173+
string s(e.expr);
174+
bool ok = false;
175+
176+
if (!s.empty() && s.front() == '(' && s.back() == ')') {
177+
string inner = s.substr(1, s.size() - 2);
178+
int d = 0;
179+
size_t pos = string::npos;
180+
for (size_t i = 0; i < inner.size(); ++i) {
181+
char c = inner[i];
182+
if (c == '(') ++d;
183+
else if (c == ')') --d;
184+
else if (c == ',' && d == 0) {
185+
pos = i;
186+
break;
187+
}
188+
}
189+
if (pos != string::npos) {
190+
string fx = trim(inner.substr(0, pos));
191+
string gx = trim(inner.substr(pos + 1));
192+
double t = 0.0;
193+
exprtk::symbol_table<double> st;
194+
st.add_constants();
195+
addConstants(st);
196+
st.add_variable("t", t);
197+
exprtk::expression<double> ef, eg;
198+
ef.register_symbol_table(st);
199+
eg.register_symbol_table(st);
200+
exprtk::parser<double> p;
201+
if (p.compile(fx, ef) && p.compile(gx, eg)) {
202+
for (t = -10.0; t <= 10.0; t += 0.02) {
203+
double vx = ef.value();
204+
double vy = eg.value();
205+
pts.push_back(ImVec2(origin.x + vx * zoom, origin.y - vy * zoom));
206+
}
207+
ImU32 c = IM_COL32(e.col.x * 255, e.col.y * 255, e.col.z * 255, 255);
208+
draw_list->AddPolyline(pts.data(), pts.size(), c, ImDrawFlags_None, lineThickness);
209+
ok = true;
201210
}
202-
203-
// Draw curve
204-
draw_list->AddPolyline(points.data(),
205-
points.size(),
206-
IM_COL32(64, 128, 199, 255),
207-
ImDrawFlags_None,
208-
lineThickness);
209-
plotted = true;
210211
}
211212
}
212-
}
213-
214-
if (!plotted) {
215-
// Fallback to y = f(x) plotting using variable x
216-
double x;
217-
218-
exprtk::symbol_table<double> symbolTable;
219-
symbolTable.add_constants();
220-
addConstants(symbolTable);
221-
symbolTable.add_variable("x", x);
222-
223-
exprtk::expression<double> expression;
224-
expression.register_symbol_table(symbolTable);
225-
226-
exprtk::parser<double> parser;
227-
parser.compile(function, expression);
228-
229-
for (x = -canvas_sz.x / (2 * zoom); x < canvas_sz.x / (2 * zoom); x += 0.05) {
230-
const double y = expression.value();
231-
232-
233-
ImVec2 screen_pos(origin.x + x * zoom, origin.y - y * zoom);
234-
points.push_back(screen_pos);
213+
if (!ok) {
214+
double x;
215+
exprtk::symbol_table<double> st;
216+
st.add_constants();
217+
addConstants(st);
218+
st.add_variable("x", x);
219+
exprtk::expression<double> expr;
220+
expr.register_symbol_table(st);
221+
exprtk::parser<double> p;
222+
if (p.compile(s, expr)) {
223+
for (x = -canvas_sz.x / (2 * zoom); x < canvas_sz.x / (2 * zoom); x += 0.05) {
224+
double y = expr.value();
225+
pts.push_back(ImVec2(origin.x + x * zoom, origin.y - y * zoom));
226+
}
227+
ImU32 c = IM_COL32(e.col.x * 255, e.col.y * 255, e.col.z * 255, 255);
228+
draw_list->AddPolyline(pts.data(), pts.size(), c, ImDrawFlags_None, lineThickness);
229+
}
235230
}
236-
237-
draw_list->AddPolyline(points.data(),
238-
points.size(),
239-
IM_COL32(199, 68, 64, 255),
240-
ImDrawFlags_None,
241-
lineThickness);
242231
}
243232

244233
ImGui::End();

0 commit comments

Comments
 (0)