Skip to content

Instantly share code, notes, and snippets.

@digitalsignalperson
Last active April 23, 2025 15:20
Show Gist options
  • Save digitalsignalperson/c935e377e407e0173ac40562039380bf to your computer and use it in GitHub Desktop.
Save digitalsignalperson/c935e377e407e0173ac40562039380bf to your computer and use it in GitHub Desktop.
simple python flask endpoint serving scatterplot data and drawn by a sokol+imgui WASM client

Setup

See tips from https://github.com/floooh/sokol-samples#how-to-build-without-a-build-system

git clone https://github.com/floooh/sokol-samples
git clone https://github.com/floooh/sokol
git clone https://github.com/floooh/sokol-tools-bin
git clone https://github.com/ocornut/imgui.git
cd imgui
git checkout v1.91.9b
cd ..
git clone https://github.com/epezent/implot.git
git clone https://github.com/nlohmann/json.git

# Make sure to install emscripten as appropriate
sudo pacman -S emscripten
# or
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
cd ..
source emsdk/emsdk_env.sh
# or source emsdk_env.fish

#clone this gist
git clone https://gist.github.com/digitalsignalperson/c935e377e407e0173ac40562039380bf.git imgui-endpoint-demo

cd sokol-samples/sapp

Test building cube sapp

../../sokol-tools-bin/bin/linux/sokol-shdc -i cube-sapp.glsl -o cube-sapp.glsl.h -l glsl300es
/usr/lib/emscripten/emcc cube-sapp.c ../libs/sokol/sokol.c -o cube-sapp.html -DSOKOL_GLES3 -I../../sokol -I../libs -sUSE_WEBGL2 --shell-file=../webpage/shell.html

/usr/lib/emscripten/emrun cube-sapp.html

Build imgui sapp

/usr/lib/emscripten/emcc imgui-sapp.cc ../libs/sokol/sokol.cc \
    ../../imgui/imgui.cpp \
    ../../imgui/imgui_demo.cpp \
    ../../imgui/imgui_draw.cpp \
    ../../imgui/imgui_tables.cpp \
    ../../imgui/imgui_widgets.cpp \
    -o imgui-sapp.html \
    -DSOKOL_GLES3 \
    -I../../sokol \
    -I../../imgui \
    -I../../sokol/util \
    -I../libs \
    -sUSE_WEBGL2 \
    --shell-file=../webpage/shell.html

/usr/lib/emscripten/emrun imgui-sapp.html

Endpoint demo

Write a simple endpoing using python and flask to serve some data for a scatter plot. Modify imgui-sapp.cc to include sokol_fetch.h to get the data from the endpoint. Modify imgui-sapp.cc to draw a scatter plot using implot

Make sure sokol_fetch.h include is enabled here

sed -i '/#include "sokol_fetch.h"/s|^//||' sokol-samples/libs/sokol/sokol.cc

And patch imgui-sapp.cc with this modded demo

cp ../../imgui-endpoint-demo/imgui-endpoint-viewer-sapp.cc imgui-sapp.cc

Now build

/usr/lib/emscripten/emcc imgui-sapp.cc ../libs/sokol/sokol.cc \
    ../../imgui/imgui.cpp \
    ../../imgui/imgui_demo.cpp \
    ../../imgui/imgui_draw.cpp \
    ../../imgui/imgui_tables.cpp \
    ../../imgui/imgui_widgets.cpp \
    ../../implot/implot.cpp \
    ../../implot/implot_items.cpp \
    -o imgui-endpoint-viewer-sapp.html \
    -DSOKOL_GLES3 \
    -I../../sokol \
    -I../../imgui \
    -I../../implot \
    -I../../json/single_include \
    -I../../sokol/util \
    -I../libs \
    -sUSE_WEBGL2 \
    -sFETCH \
    --shell-file=../webpage/shell.html

Run the server and the client

python ../../imgui-endpoint-demo/server.py

/usr/lib/emscripten/emrun imgui-endpoint-viewer-sapp.html

image

To build a naive executable instead:

g++ imgui-sapp.cc ../libs/sokol/sokol.cc \
    ../../imgui/imgui.cpp \
    ../../imgui/imgui_demo.cpp \
    ../../imgui/imgui_draw.cpp \
    ../../imgui/imgui_tables.cpp \
    ../../imgui/imgui_widgets.cpp \
    ../../implot/implot.cpp \
    ../../implot/implot_items.cpp \
    -o imgui-sapp \
    -I../../sokol \
    -I../../imgui \
    -I../../implot \
    -I../../json/single_include \
    -I../../sokol/util \
    -I../libs \
    -DSOKOL_GLCORE \
    -lGL -ldl -lX11 -lXi -lXcursor -lasound -pthread \
    -std=c++17 \
    -Wall -Wextra
    
 ./imgui-sapp
//------------------------------------------------------------------------------
// imgui-endpoint-viewer-sapp.cc
//------------------------------------------------------------------------------
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_log.h"
#include "sokol_glue.h"
#include "sokol_fetch.h"
#include "imgui.h"
#include "implot.h"
#define SOKOL_IMGUI_IMPL
#include "sokol_imgui.h"
#include "nlohmann/json.hpp"
#include <string>
#include <vector>
using json = nlohmann::json;
// Structure to hold our scatter plot data
struct ScatterData {
std::vector<float> x;
std::vector<float> y;
int count;
bool loaded;
std::string error;
};
static bool show_test_window = true;
static bool show_scatter_window = true;
static sg_pass_action pass_action;
static ScatterData scatter_data = {
std::vector<float>(),
std::vector<float>(),
0,
false,
""
};
// Buffer for fetch response
static uint8_t fetch_buffer[512 * 1024];
static void fetch_callback(const sfetch_response_t* response) {
if (response->fetched) {
try {
// Ensure null termination of response data
((char*)response->data.ptr)[response->data.size] = 0;
// Parse JSON response using nlohmann/json
auto j = json::parse((char*)response->data.ptr);
scatter_data.x = j["x"].get<std::vector<float>>();
scatter_data.y = j["y"].get<std::vector<float>>();
scatter_data.count = j["count"].get<int>();
scatter_data.loaded = true;
scatter_data.error.clear();
} catch (const std::exception& e) {
scatter_data.error = std::string("JSON parse error: ") + e.what();
}
} else if (response->failed) {
scatter_data.error = "Failed to fetch data";
}
}
void init(void) {
// setup sokol-gfx
sg_desc desc = {};
desc.environment = sglue_environment();
desc.logger.func = slog_func;
sg_setup(&desc);
// setup sokol-fetch
sfetch_desc_t fetch_desc = {};
fetch_desc.max_requests = 1;
fetch_desc.num_channels = 1;
fetch_desc.num_lanes = 1;
fetch_desc.logger.func = slog_func;
sfetch_setup(&fetch_desc);
// setup sokol-imgui
simgui_desc_t simgui_desc = {};
simgui_desc.logger.func = slog_func;
simgui_setup(&simgui_desc);
// Setup ImPlot
ImPlot::CreateContext();
// initial clear color
pass_action.colors[0].load_action = SG_LOADACTION_CLEAR;
pass_action.colors[0].clear_value = { 0.0f, 0.5f, 0.7f, 1.0f };
// Start loading data
sfetch_request_t req = {};
req.path = "http://localhost:5000/scatter_data";
req.callback = fetch_callback;
req.buffer = SFETCH_RANGE(fetch_buffer);
sfetch_send(&req);
}
void frame(void) {
sfetch_dowork();
const int width = sapp_width();
const int height = sapp_height();
simgui_new_frame({ width, height, sapp_frame_duration(), sapp_dpi_scale() });
// Show the scatter plot window
if (show_scatter_window) {
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
ImGui::Begin("Scatter Plot", &show_scatter_window);
if (scatter_data.loaded && !scatter_data.x.empty()) {
if (ImPlot::BeginPlot("Scatter Plot")) {
ImPlot::PlotScatter("Random Data",
scatter_data.x.data(),
scatter_data.y.data(),
scatter_data.x.size());
ImPlot::EndPlot();
}
} else if (!scatter_data.error.empty()) {
ImGui::TextColored(ImVec4(1,0,0,1), "Error: %s", scatter_data.error.c_str());
} else {
ImGui::Text("Loading data...");
}
if (ImGui::Button("Refresh Data")) {
scatter_data.loaded = false;
scatter_data.error.clear();
sfetch_request_t req = {};
req.path = "http://localhost:5000/scatter_data";
req.callback = fetch_callback;
req.buffer = SFETCH_RANGE(fetch_buffer);
sfetch_send(&req);
}
ImGui::End();
}
// Demo window (optional)
if (show_test_window) {
ImGui::ShowDemoWindow(&show_test_window);
}
// the sokol_gfx draw pass
sg_pass pass = {};
pass.action = pass_action;
pass.swapchain = sglue_swapchain();
sg_begin_pass(&pass);
simgui_render();
sg_end_pass();
sg_commit();
}
void cleanup(void) {
ImPlot::DestroyContext();
simgui_shutdown();
sfetch_shutdown();
sg_shutdown();
}
void input(const sapp_event* event) {
simgui_handle_event(event);
}
sapp_desc sokol_main(int argc, char* argv[]) {
(void)argc; (void)argv;
sapp_desc desc = {};
desc.init_cb = init;
desc.frame_cb = frame;
desc.cleanup_cb = cleanup;
desc.event_cb = input;
desc.window_title = "ImGui Scatter Plot (sokol-app)";
desc.ios_keyboard_resizes_canvas = false;
desc.icon.sokol_default = true;
desc.enable_clipboard = true;
desc.logger.func = slog_func;
return desc;
}
diff --git a/sapp/imgui-sapp.cc b/sapp/imgui-sapp.cc
index 514552b..25bab5d 100644
--- a/sapp/imgui-sapp.cc
+++ b/sapp/imgui-sapp.cc
@@ -1,74 +1,144 @@
//------------------------------------------------------------------------------
-// imgui-sapp.c
-//
-// Demonstrates Dear ImGui UI rendering via sokol_gfx.h and
-// the utility header sokol_imgui.h.
-//
-// Also tests default window size by keeping sapp_desc.width/height
-// zero-initialized.
+// imgui-endpoint-viewer-sapp.cc
//------------------------------------------------------------------------------
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_log.h"
#include "sokol_glue.h"
+#include "sokol_fetch.h"
#include "imgui.h"
+#include "implot.h"
#define SOKOL_IMGUI_IMPL
#include "sokol_imgui.h"
+#include "nlohmann/json.hpp"
+#include <string>
+#include <vector>
-static bool show_test_window = true;
-static bool show_another_window = false;
+using json = nlohmann::json;
+
+// Structure to hold our scatter plot data
+struct ScatterData {
+ std::vector<float> x;
+ std::vector<float> y;
+ int count;
+ bool loaded;
+ std::string error;
+};
+static bool show_test_window = true;
+static bool show_scatter_window = true;
static sg_pass_action pass_action;
+static ScatterData scatter_data = {
+ std::vector<float>(),
+ std::vector<float>(),
+ 0,
+ false,
+ ""
+};
+
+// Buffer for fetch response
+static uint8_t fetch_buffer[512 * 1024];
+
+static void fetch_callback(const sfetch_response_t* response) {
+ if (response->fetched) {
+ try {
+ // Ensure null termination of response data
+ ((char*)response->data.ptr)[response->data.size] = 0;
+
+ // Parse JSON response using nlohmann/json
+ auto j = json::parse((char*)response->data.ptr);
+
+ scatter_data.x = j["x"].get<std::vector<float>>();
+ scatter_data.y = j["y"].get<std::vector<float>>();
+ scatter_data.count = j["count"].get<int>();
+ scatter_data.loaded = true;
+ scatter_data.error.clear();
+ } catch (const std::exception& e) {
+ scatter_data.error = std::string("JSON parse error: ") + e.what();
+ }
+ } else if (response->failed) {
+ scatter_data.error = "Failed to fetch data";
+ }
+}
void init(void) {
- // setup sokol-gfx, sokol-time and sokol-imgui
- sg_desc desc = { };
+ // setup sokol-gfx
+ sg_desc desc = {};
desc.environment = sglue_environment();
desc.logger.func = slog_func;
sg_setup(&desc);
- // use sokol-imgui with all default-options (we're not doing
- // multi-sampled rendering or using non-default pixel formats)
- simgui_desc_t simgui_desc = { };
+ // setup sokol-fetch
+ sfetch_desc_t fetch_desc = {};
+ fetch_desc.max_requests = 1;
+ fetch_desc.num_channels = 1;
+ fetch_desc.num_lanes = 1;
+ fetch_desc.logger.func = slog_func;
+ sfetch_setup(&fetch_desc);
+
+ // setup sokol-imgui
+ simgui_desc_t simgui_desc = {};
simgui_desc.logger.func = slog_func;
simgui_setup(&simgui_desc);
+ // Setup ImPlot
+ ImPlot::CreateContext();
+
// initial clear color
pass_action.colors[0].load_action = SG_LOADACTION_CLEAR;
pass_action.colors[0].clear_value = { 0.0f, 0.5f, 0.7f, 1.0f };
+
+ // Start loading data
+ sfetch_request_t req = {};
+ req.path = "http://localhost:5000/scatter_data";
+ req.callback = fetch_callback;
+ req.buffer = SFETCH_RANGE(fetch_buffer);
+ sfetch_send(&req);
}
void frame(void) {
+ sfetch_dowork();
+
const int width = sapp_width();
const int height = sapp_height();
simgui_new_frame({ width, height, sapp_frame_duration(), sapp_dpi_scale() });
- // 1. Show a simple window
- // Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets appears in a window automatically called "Debug"
- static float f = 0.0f;
- ImGui::Text("Hello, world!");
- ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
- ImGui::ColorEdit3("clear color", &pass_action.colors[0].clear_value.r);
- if (ImGui::Button("Test Window")) show_test_window ^= 1;
- if (ImGui::Button("Another Window")) show_another_window ^= 1;
- ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
- ImGui::Text("w: %d, h: %d, dpi_scale: %.1f", sapp_width(), sapp_height(), sapp_dpi_scale());
- if (ImGui::Button(sapp_is_fullscreen() ? "Switch to windowed" : "Switch to fullscreen")) {
- sapp_toggle_fullscreen();
- }
+ // Show the scatter plot window
+ if (show_scatter_window) {
+ ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_FirstUseEver);
+ ImGui::Begin("Scatter Plot", &show_scatter_window);
+
+ if (scatter_data.loaded && !scatter_data.x.empty()) {
+ if (ImPlot::BeginPlot("Scatter Plot")) {
+ ImPlot::PlotScatter("Random Data",
+ scatter_data.x.data(),
+ scatter_data.y.data(),
+ scatter_data.x.size());
+ ImPlot::EndPlot();
+ }
+ } else if (!scatter_data.error.empty()) {
+ ImGui::TextColored(ImVec4(1,0,0,1), "Error: %s", scatter_data.error.c_str());
+ } else {
+ ImGui::Text("Loading data...");
+ }
+
+ if (ImGui::Button("Refresh Data")) {
+ scatter_data.loaded = false;
+ scatter_data.error.clear();
+
+ sfetch_request_t req = {};
+ req.path = "http://localhost:5000/scatter_data";
+ req.callback = fetch_callback;
+ req.buffer = SFETCH_RANGE(fetch_buffer);
+ sfetch_send(&req);
+ }
- // 2. Show another simple window, this time using an explicit Begin/End pair
- if (show_another_window) {
- ImGui::SetNextWindowSize(ImVec2(200,100), ImGuiCond_FirstUseEver);
- ImGui::Begin("Another Window", &show_another_window);
- ImGui::Text("Hello");
ImGui::End();
}
- // 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowDemoWindow()
+ // Demo window (optional)
if (show_test_window) {
- ImGui::SetNextWindowPos(ImVec2(460, 20), ImGuiCond_FirstUseEver);
- ImGui::ShowDemoWindow();
+ ImGui::ShowDemoWindow(&show_test_window);
}
// the sokol_gfx draw pass
@@ -82,7 +152,9 @@ void frame(void) {
}
void cleanup(void) {
+ ImPlot::DestroyContext();
simgui_shutdown();
+ sfetch_shutdown();
sg_shutdown();
}
@@ -92,12 +164,12 @@ void input(const sapp_event* event) {
sapp_desc sokol_main(int argc, char* argv[]) {
(void)argc; (void)argv;
- sapp_desc desc = { };
+ sapp_desc desc = {};
desc.init_cb = init;
desc.frame_cb = frame;
desc.cleanup_cb = cleanup;
desc.event_cb = input;
- desc.window_title = "Dear ImGui (sokol-app)";
+ desc.window_title = "ImGui Scatter Plot (sokol-app)";
desc.ios_keyboard_resizes_canvas = false;
desc.icon.sokol_default = true;
desc.enable_clipboard = true;
from flask import Flask, jsonify
import numpy as np
from flask_cors import CORS
app = Flask(__name__)
CORS(app) # Enable CORS for all routes
@app.route('/scatter_data')
def scatter_data():
# Generate 100 random points
n_points = 100
x = np.random.normal(0, 1, n_points).tolist()
y = np.random.normal(0, 1, n_points).tolist()
return jsonify({
'x': x,
'y': y,
'count': n_points
})
if __name__ == '__main__':
app.run(port=5000)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment