Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ endif()
if(PJ_INSTALL_SDK)
include(CMakePackageConfigHelpers)

set(PJ_PACKAGE_VERSION "0.11.0")
set(PJ_PACKAGE_VERSION "0.12.0")
set(PJ_PACKAGE_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/plotjuggler_sdk)

install(EXPORT plotjuggler_sdkTargets
Expand Down
2 changes: 1 addition & 1 deletion conanfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

class PlotjugglerSdkConan(ConanFile):
name = "plotjuggler_sdk"
version = "0.11.0"
version = "0.12.0"
# Apache-2.0 covers the whole SDK (pj_base + pj_plugins). See LICENSE.
license = "Apache-2.0"
url = "https://github.com/PlotJuggler/plotjuggler_sdk"
Expand Down
22 changes: 22 additions & 0 deletions pj_base/include/pj_base/plugin_data_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,28 @@ typedef struct PJ_data_processors_host_vtable_t {
* call on this vtable. An unknown id is an error. */
bool (*data_processor_config)(
void* ctx, PJ_string_view_t id, PJ_string_view_t* out_recipe_json, PJ_error_t* out_error) PJ_NOEXCEPT;

/* [main-thread] Create an EPHEMERAL transform node — identical to
* create_data_processor but the node is never persisted, never catalogued,
* and is automatically torn down when remove_data_processor is called with
* the same id. Intended for live preview: create on each keystroke, remove
* on cancel/close. Tail-appended: guard with struct_size before calling. */
bool (*create_data_processor_ephemeral)(
void* ctx, PJ_string_view_t id, const PJ_string_view_t* inputs, uint64_t input_count,
const PJ_string_view_t* outputs, uint64_t output_count, PJ_string_view_t script, PJ_string_view_t params_json,
PJ_error_t* out_error) PJ_NOEXCEPT;

/* [main-thread] Validate a transform script WITHOUT installing anything: the
* host compiles it with the backend named by `language` ("luau" today; "python"
* reserved for a future backend) to catch syntax and module-load errors. Returns
* true if the script compiles; on false, *out_error carries the reason (suitable
* for a UI semaphore / error box). Compilation-only: it does NOT run the script
* over data, so a runtime error or empty output is not detected here. An unknown
* language is an error. No node, topic or catalog entry is created. Tail-appended:
* guard with struct_size before calling. */
bool (*validate_data_processor_script)(
void* ctx, PJ_string_view_t script, PJ_string_view_t language, PJ_string_view_t params_json,
PJ_error_t* out_error) PJ_NOEXCEPT;
} PJ_data_processors_host_vtable_t;

typedef struct {
Expand Down
51 changes: 51 additions & 0 deletions pj_base/include/pj_base/sdk/plugin_data_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,57 @@ class DataProcessorsHostView {
return std::string(toStringView(recipe));
}

/// Create an EPHEMERAL transform node for live preview. Identical to
/// createTransform but the node is never persisted or catalogued.
/// Call remove() with the same id on cancel/close to tear it down.
/// Returns an error if the host does not support this slot (SDK < 0.12.0).
[[nodiscard]] Status createEphemeralTransform(
std::string_view id, Span<const std::string_view> inputs, Span<const std::string_view> outputs,
std::string_view script, std::string_view params_json) const {
if (!valid() ||
!PJ_HAS_TAIL_SLOT(PJ_data_processors_host_vtable_t, host_.vtable, create_data_processor_ephemeral)) {
return unexpected("host does not support ephemeral transforms (SDK < 0.12.0)");
}
if (outputs.empty()) {
return unexpected("data processors transform requires at least one output topic");
}
std::vector<PJ_string_view_t> in_abi;
in_abi.reserve(inputs.size());
for (const auto& name : inputs) {
in_abi.push_back(toAbiString(name));
}
std::vector<PJ_string_view_t> out_abi;
out_abi.reserve(outputs.size());
for (const auto& name : outputs) {
out_abi.push_back(toAbiString(name));
}
PJ_error_t err{};
if (!host_.vtable->create_data_processor_ephemeral(
host_.ctx, toAbiString(id), in_abi.data(), in_abi.size(), out_abi.data(), out_abi.size(),
toAbiString(script), toAbiString(params_json), &err)) {
return unexpected(errorToString(err));
}
return okStatus();
}

/// Validate a transform script without installing anything. `language` selects
/// the backend ("luau" today; "python" reserved). Returns ok if the script
/// compiles; otherwise the host's error message (ready for a semaphore / error
/// box). Compilation-only — it does not run the script over data. Errors if the
/// host predates this slot (SDK < 0.12.0) or the language is unknown.
[[nodiscard]] Status validateScript(
std::string_view script, std::string_view language, std::string_view params_json = "{}") const {
if (!valid() || !PJ_HAS_TAIL_SLOT(PJ_data_processors_host_vtable_t, host_.vtable, validate_data_processor_script)) {
return unexpected("host does not support script validation (SDK < 0.12.0)");
}
PJ_error_t err{};
if (!host_.vtable->validate_data_processor_script(
host_.ctx, toAbiString(script), toAbiString(language), toAbiString(params_json), &err)) {
return unexpected(errorToString(err));
}
return okStatus();
}

private:
PJ_data_processors_host_t host_{};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ class WidgetDataView {
std::string label;
std::vector<std::pair<double, double>> points; // {x, y}
std::string color; // optional hex "#rrggbb"; empty means use chart theme default
bool dashed = false; // draw with a dashed line
};

[[nodiscard]] std::optional<std::vector<ChartSeriesView>> chartSeries(std::string_view name) const {
Expand Down Expand Up @@ -190,6 +191,10 @@ class WidgetDataView {
if (color_it != s.end() && color_it->is_string()) {
sv.color = color_it->get<std::string>();
}
auto dashed_it = s.find("dashed");
if (dashed_it != s.end() && dashed_it->is_boolean()) {
sv.dashed = dashed_it->get<bool>();
}
result.push_back(std::move(sv));
}
return result;
Expand All @@ -200,6 +205,11 @@ class WidgetDataView {
return getBool(name, "chart_zoom_enabled");
}

/// Returns whether the chart should auto-fit on every series update.
[[nodiscard]] std::optional<bool> chartAutoZoom(std::string_view name) const {
return getBool(name, "chart_auto_zoom");
}

// --- QPlainTextEdit ---
[[nodiscard]] std::optional<std::string> plainText(std::string_view name) const {
return getString(name, "plain_text");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ struct ChartPoint {
struct ChartSeries {
std::string label;
std::vector<ChartPoint> points;
std::string color; // optional hex "#rrggbb"
std::string color; // optional hex "#rrggbb"
bool dashed = false; // draw with a dashed line (e.g. a faded "before" ghost curve)
};

/// One boundary segment on a RangeSlider (used by setRangeSliderMarkers): a box
Expand Down Expand Up @@ -192,6 +193,9 @@ class WidgetData {
if (!s.color.empty()) {
entry["color"] = s.color;
}
if (s.dashed) {
entry["dashed"] = true;
}
arr.push_back(std::move(entry));
}
e["chart_series"] = std::move(arr);
Expand All @@ -211,6 +215,14 @@ class WidgetData {
return *this;
}

/// Auto-fit (zoom-to-extents) the chart inside the named QFrame on every series
/// update when `enabled` is true; when false, preserve the user's current zoom.
/// Mirrors the Transform/Filter editor "AutoZoom" checkbox.
WidgetData& setChartAutoZoom(std::string_view name, bool enabled) {
entry(name)["chart_auto_zoom"] = enabled;
return *this;
}

// --- QPlainTextEdit ---
WidgetData& setPlainText(std::string_view name, std::string_view text) {
entry(name)["plain_text"] = text;
Expand Down
Loading