GUI Controls¶
This example shows Datoviz GUI controls updating a point visual.
Preview¶

Run And Adapt¶
Commands below assume a Datoviz source checkout and start at the repository root. Use your configured build environment; Python routes additionally require local bindings.
| Route | Availability | Command or action |
|---|---|---|
| C | Canonical native source | just example-c features/gui_controls (build and run), or rerun ./build/examples/c/features/gui_controls |
| Python | Available; direct-engine adaptation | python3 -m examples.python.gallery.features.gui_controls |
| Browser | Native only | Datoviz GUI controls require the native ImGui/GLFW app path |
This example is approved as a starting point for user code and coding agents. Keep the object lifetimes and data shapes intact while adapting the data and styling.
What To Look For¶
Five point positions are fixed, while GUI sliders and color editors update the color and diameter_px arrays and the Visible checkbox toggles the visual. The extra synthetic data, effects, volume, and diagnostics controls exercise common widget types without changing the plotted points. Compare controls that visibly affect the markers with mock controls that only update state; this is useful for building analysis panels where some widgets drive data uploads and others configure future processing.
Source¶
#!/usr/bin/env python3
"""Native Datoviz GUI controls updating a retained point visual."""
from __future__ import annotations
import ctypes
import numpy as np
import datoviz as dvz
from examples.python.gallery import common as ex
POINT_COUNT = 5
PALETTE_ITEMS = (ctypes.c_char_p * 4)(b"Cyan", b"Amber", b"Violet", b"Slate")
class GuiControlsState:
def __init__(self, point) -> None:
self.point = point
self.diameter_px = ctypes.c_float(42.0)
self.color = (ctypes.c_float * 4)(0.28, 0.78, 1.00, 1.00)
self.visible = ctypes.c_bool(True)
self.pulse = ctypes.c_bool(True)
self.palette = ctypes.c_int(0)
self.glyph_count = ctypes.c_int(96)
self.opacity = ctypes.c_float(1.0)
self.jitter = (ctypes.c_float * 2)(0.12, -0.08)
self.contrast = (ctypes.c_float * 4)(0.08, 0.32, 0.72, 0.94)
self.bloom_enabled = ctypes.c_bool(True)
self.bloom_radius = ctypes.c_float(3.0)
self.bloom_threshold = ctypes.c_float(0.62)
self.contour_enabled = ctypes.c_bool(False)
self.contour_width = ctypes.c_float(1.4)
self.contour_min = ctypes.c_float(0.18)
self.contour_max = ctypes.c_float(0.82)
self.diagnostic_overlay = ctypes.c_bool(False)
self.show_histogram = ctypes.c_bool(True)
self.clip_min = [ctypes.c_float(0.05), ctypes.c_float(0.05), ctypes.c_float(0.05)]
self.clip_max = [ctypes.c_float(0.95), ctypes.c_float(0.95), ctypes.c_float(0.95)]
self.light_direction = (ctypes.c_float * 3)(-0.35, 0.55, 0.75)
def _upload(state: GuiControlsState) -> None:
rgba = np.array(
[
int(np.clip(255.0 * state.color[0], 0, 255)),
int(np.clip(255.0 * state.color[1], 0, 255)),
int(np.clip(255.0 * state.color[2], 0, 255)),
int(np.clip(255.0 * state.opacity.value, 0, 255)),
],
dtype=np.uint8,
)
colors = np.tile(rgba, (POINT_COUNT, 1))
diameters = np.full(POINT_COUNT, state.diameter_px.value, dtype=np.float32)
if dvz.dvz_visual_set_data_many(
state.point,
{
"color": colors,
"diameter_px": diameters,
},
) != 0:
raise RuntimeError("dvz_visual_set_data_many(point style) failed")
def _point_positions() -> np.ndarray:
return np.array(
[
[-0.70, -0.35, 0.0],
[-0.35, +0.20, 0.0],
[+0.00, -0.10, 0.0],
[+0.35, +0.35, 0.0],
[+0.70, -0.20, 0.0],
],
dtype=np.float32,
)
def _build_scene():
scene, figure, panel = ex.scene_panel()
point = dvz.dvz_point(scene, 0)
if not point:
raise RuntimeError("dvz_point() failed")
state = GuiControlsState(point)
if dvz.dvz_visual_set_data(point, "position", _point_positions()) != 0:
raise RuntimeError("dvz_visual_set_data(position) failed")
_upload(state)
ex.set_filled_point_style(point)
if dvz.dvz_visual_set_depth_test(point, False) != 0:
raise RuntimeError("dvz_visual_set_depth_test() failed")
ex.add_visual(panel, point)
return scene, figure, panel, state
def _reset_mock_values(state: GuiControlsState) -> None:
state.bloom_radius.value = 3.0
state.bloom_threshold.value = 0.62
state.contour_width.value = 1.4
state.contour_min.value = 0.18
state.contour_max.value = 0.82
for value in state.clip_min:
value.value = 0.05
for value in state.clip_max:
value.value = 0.95
def _gui_callback_factory(state: GuiControlsState):
def gui_callback(gui, _view, _user_data) -> None:
changed = False
visible_changed = False
if dvz.dvz_gui_begin(gui, b"Widget controls", None, 0):
dvz.dvz_gui_separator_text(gui, b"Marker")
changed |= dvz.dvz_gui_slider_float(
gui,
b"Diameter##gui_controls_marker_diameter",
ctypes.byref(state.diameter_px),
8.0,
96.0,
)
changed |= dvz.dvz_gui_color_edit4(
gui, b"Tint##gui_controls_marker_tint", state.color, 0
)
changed |= dvz.dvz_gui_slider_float(
gui, b"Alpha##gui_controls_marker_alpha", ctypes.byref(state.opacity), 0.15, 1.0
)
visible_changed |= dvz.dvz_gui_checkbox(
gui, b"Visible##gui_controls_marker_visible", ctypes.byref(state.visible)
)
dvz.dvz_gui_checkbox(gui, b"Pulse preview##gui_controls_marker_pulse", ctypes.byref(state.pulse))
dvz.dvz_gui_separator_text(gui, b"Synthetic data")
dvz.dvz_gui_combo(
gui,
b"Palette##gui_controls_data_palette",
ctypes.byref(state.palette),
PALETTE_ITEMS,
len(PALETTE_ITEMS),
)
dvz.dvz_gui_slider_int(
gui,
b"Sample count##gui_controls_data_sample_count",
ctypes.byref(state.glyph_count),
16,
256,
)
dvz.dvz_gui_slider_float2(
gui, b"Jitter XY##gui_controls_data_jitter_xy", state.jitter, -1.0, 1.0
)
dvz.dvz_gui_slider_float4(
gui, b"Contrast curve##gui_controls_data_contrast_curve", state.contrast, 0.0, 1.0
)
if dvz.dvz_gui_collapsing_header(gui, b"Mock effects##gui_controls_effects_section", 0):
dvz.dvz_gui_checkbox(
gui,
b"Bloom enabled##gui_controls_effects_bloom_enabled",
ctypes.byref(state.bloom_enabled),
)
dvz.dvz_gui_slider_float_format(
gui,
b"Bloom radius##gui_controls_effects_bloom_radius",
ctypes.byref(state.bloom_radius),
0.5,
12.0,
b"%.1f px",
)
dvz.dvz_gui_slider_float(
gui,
b"Bloom threshold##gui_controls_effects_bloom_threshold",
ctypes.byref(state.bloom_threshold),
0.0,
1.0,
)
dvz.dvz_gui_checkbox(
gui,
b"Contours enabled##gui_controls_effects_contours_enabled",
ctypes.byref(state.contour_enabled),
)
dvz.dvz_gui_slider_float(
gui,
b"Contour width##gui_controls_effects_contour_width",
ctypes.byref(state.contour_width),
0.25,
5.0,
)
dvz.dvz_gui_slider_range_float(
gui,
b"Contour range##gui_controls_effects_contour_range",
ctypes.byref(state.contour_min),
ctypes.byref(state.contour_max),
0.0,
1.0,
b"%.2f",
)
if dvz.dvz_gui_collapsing_header(gui, b"Mock volume##gui_controls_volume_section", 0):
dvz.dvz_gui_slider_float3(
gui,
b"Light vector##gui_controls_volume_light_vector",
state.light_direction,
-1.0,
1.0,
)
for axis, label in enumerate((b"Clip X", b"Clip Y", b"Clip Z")):
dvz.dvz_gui_range_float(
gui,
label + b"##gui_controls_volume_clip_" + bytes([ord("x") + axis]),
ctypes.byref(state.clip_min[axis]),
ctypes.byref(state.clip_max[axis]),
0.01,
0.0,
1.0,
b"%.2f",
)
dvz.dvz_gui_separator_text(gui, b"Diagnostics")
dvz.dvz_gui_checkbox(
gui,
b"Overlay##gui_controls_diagnostics_overlay",
ctypes.byref(state.diagnostic_overlay),
)
dvz.dvz_gui_same_line(gui, 0.0, -1.0)
dvz.dvz_gui_checkbox(
gui,
b"Histogram##gui_controls_diagnostics_histogram",
ctypes.byref(state.show_histogram),
)
if dvz.dvz_gui_button(gui, b"Reset mock values##gui_controls_diagnostics_reset"):
_reset_mock_values(state)
dvz.dvz_gui_end(gui)
if changed:
_upload(state)
if visible_changed:
dvz.dvz_visual_set_visible(state.point, state.visible.value)
return gui_callback
def _configure_view(view, state: GuiControlsState, *, gui: bool = True) -> None:
if not gui:
return
overlay = dvz.dvz_view_gui(view, None)
if not overlay:
return
if dvz.dvz_view_set_gui_callback(view, _gui_callback_factory(state), None) != 0:
raise RuntimeError("dvz_view_set_gui_callback() failed")
def main() -> None:
scene, figure, _panel, state = _build_scene()
def configure(view) -> None:
_configure_view(view, state)
ex.run_with_view(scene, figure, "GUI Controls", configure)
if __name__ == "__main__":
main()
/*
* Copyright (c) 2021 Cyrille Rossant and contributors. All rights reserved.
* Licensed under the MIT license. See LICENSE file in the project root for details.
* SPDX-License-Identifier: MIT
*/
/* gui_controls - This example shows Datoviz GUI controls updating a retained point visual.
*
* Scenario: features_gui_controls
* Style: features, native GUI/app
*
* Build: just example-c features/gui_controls
* Run: ./build/examples/c/features/gui_controls
*
* What to look for: five point positions are fixed, while GUI sliders and color editors update the
* color and diameter_px arrays and the Visible checkbox toggles the visual. The extra synthetic
* data, effects, volume, and diagnostics controls exercise common widget types without changing
* the plotted points. Compare controls that visibly affect the markers with mock controls that only
* update state; this is useful for building analysis panels where some widgets drive data uploads
* and others configure future processing.
*/
/*************************************************************************************************/
/* Includes */
/*************************************************************************************************/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "datoviz/app.h"
#include "datoviz/gui.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_style.h"
/*************************************************************************************************/
/* Constants */
/*************************************************************************************************/
#define WIDTH EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT
#define POINT_COUNT 5u
/*************************************************************************************************/
/* Structs */
/*************************************************************************************************/
typedef struct GuiControlsState
{
DvzVisual* point;
float diameter_px;
float color[4];
bool visible;
bool pulse;
int palette;
int glyph_count;
float opacity;
float jitter[2];
float contrast[4];
bool bloom_enabled;
float bloom_radius;
float bloom_threshold;
bool contour_enabled;
float contour_width;
float contour_range[2];
bool diagnostic_overlay;
bool show_histogram;
float clip_min[3];
float clip_max[3];
float light_direction[3];
} GuiControlsState;
/*************************************************************************************************/
/* Helpers */
/*************************************************************************************************/
/**
* Upload the point colors and sizes controlled by the GUI.
*
* @param state GUI controls example state
* @return true on success
*/
static bool _gui_controls_upload(GuiControlsState* state)
{
if (state == NULL || state->point == NULL)
return false;
DvzColor colors[POINT_COUNT] = {0};
float diameters[POINT_COUNT] = {0};
for (uint32_t i = 0; i < POINT_COUNT; i++)
{
colors[i].r = (uint8_t)(255.0f * state->color[0]);
colors[i].g = (uint8_t)(255.0f * state->color[1]);
colors[i].b = (uint8_t)(255.0f * state->color[2]);
colors[i].a = (uint8_t)(255.0f * state->opacity);
diameters[i] = state->diameter_px;
}
DvzVisualDataUpdate updates[] = {
{.attr_name = "color", .data = colors, .item_count = POINT_COUNT},
{.attr_name = "diameter_px", .data = diameters, .item_count = POINT_COUNT},
};
return dvz_visual_set_data_many(state->point, updates, 2) == 0;
}
/**
* Build the Datoviz GUI controls for one retained visual.
*
* @param gui GUI overlay
* @param view app view
* @param user_data GUI controls example state
*/
static void _gui_controls_callback(DvzGui* gui, DvzView* view, void* user_data)
{
(void)view;
GuiControlsState* state = (GuiControlsState*)user_data;
if (state == NULL)
return;
bool changed = false;
bool visible_changed = false;
if (dvz_gui_begin(gui, "Widget controls", NULL, 0))
{
dvz_gui_separator_text(gui, "Marker");
changed |=
dvz_gui_slider_float(gui, "Diameter##gui_controls_marker_diameter", &state->diameter_px,
8.0f, 96.0f);
changed |= dvz_gui_color_edit4(gui, "Tint##gui_controls_marker_tint", state->color, 0);
changed |=
dvz_gui_slider_float(gui, "Alpha##gui_controls_marker_alpha", &state->opacity, 0.15f,
1.0f);
visible_changed |=
dvz_gui_checkbox(gui, "Visible##gui_controls_marker_visible", &state->visible);
(void)dvz_gui_checkbox(gui, "Pulse preview##gui_controls_marker_pulse", &state->pulse);
dvz_gui_separator_text(gui, "Synthetic data");
static const char* const palette_items[] = {"Cyan", "Amber", "Violet", "Slate"};
(void)dvz_gui_combo(
gui, "Palette##gui_controls_data_palette", &state->palette, palette_items, 4);
(void)dvz_gui_slider_int(
gui, "Sample count##gui_controls_data_sample_count", &state->glyph_count, 16, 256);
(void)dvz_gui_slider_float2(
gui, "Jitter XY##gui_controls_data_jitter_xy", state->jitter, -1.0f, 1.0f);
(void)dvz_gui_slider_float4(
gui, "Contrast curve##gui_controls_data_contrast_curve", state->contrast, 0.0f, 1.0f);
if (dvz_gui_collapsing_header(gui, "Mock effects##gui_controls_effects_section", 0))
{
(void)dvz_gui_checkbox(
gui, "Bloom enabled##gui_controls_effects_bloom_enabled",
&state->bloom_enabled);
(void)dvz_gui_slider_float_format(
gui, "Bloom radius##gui_controls_effects_bloom_radius", &state->bloom_radius,
0.5f, 12.0f, "%.1f px");
(void)dvz_gui_slider_float(
gui, "Bloom threshold##gui_controls_effects_bloom_threshold",
&state->bloom_threshold, 0.0f, 1.0f);
(void)dvz_gui_checkbox(
gui, "Contours enabled##gui_controls_effects_contours_enabled",
&state->contour_enabled);
(void)dvz_gui_slider_float(
gui, "Contour width##gui_controls_effects_contour_width",
&state->contour_width, 0.25f, 5.0f);
(void)dvz_gui_slider_range_float(
gui, "Contour range##gui_controls_effects_contour_range",
&state->contour_range[0], &state->contour_range[1], 0.0f, 1.0f, "%.2f");
}
if (dvz_gui_collapsing_header(gui, "Mock volume##gui_controls_volume_section", 0))
{
(void)dvz_gui_slider_float3(
gui, "Light vector##gui_controls_volume_light_vector", state->light_direction,
-1.0f, 1.0f);
(void)dvz_gui_range_float(
gui, "Clip X##gui_controls_volume_clip_x", &state->clip_min[0],
&state->clip_max[0], 0.01f, 0.0f, 1.0f, "%.2f");
(void)dvz_gui_range_float(
gui, "Clip Y##gui_controls_volume_clip_y", &state->clip_min[1],
&state->clip_max[1], 0.01f, 0.0f, 1.0f, "%.2f");
(void)dvz_gui_range_float(
gui, "Clip Z##gui_controls_volume_clip_z", &state->clip_min[2],
&state->clip_max[2], 0.01f, 0.0f, 1.0f, "%.2f");
}
dvz_gui_separator_text(gui, "Diagnostics");
(void)dvz_gui_checkbox(
gui, "Overlay##gui_controls_diagnostics_overlay", &state->diagnostic_overlay);
dvz_gui_same_line(gui, 0.0f, -1.0f);
(void)dvz_gui_checkbox(
gui, "Histogram##gui_controls_diagnostics_histogram", &state->show_histogram);
if (dvz_gui_button(gui, "Reset mock values##gui_controls_diagnostics_reset"))
{
state->bloom_radius = 3.0f;
state->bloom_threshold = 0.62f;
state->contour_width = 1.4f;
state->contour_range[0] = 0.18f;
state->contour_range[1] = 0.82f;
state->clip_min[0] = state->clip_min[1] = state->clip_min[2] = 0.05f;
state->clip_max[0] = state->clip_max[1] = state->clip_max[2] = 0.95f;
}
}
dvz_gui_end(gui);
if (changed && !_gui_controls_upload(state))
dvz_fprintf(stderr, "gui_controls: failed to upload visual data\n");
if (visible_changed)
dvz_visual_set_visible(state->point, state->visible);
}
/*************************************************************************************************/
/* Main */
/*************************************************************************************************/
int main(int argc, char** argv)
{
int ret = 1;
DvzScene* scene = NULL;
DvzApp* app = NULL;
scene = dvz_scene();
EXAMPLE_CHECK(scene != NULL, "dvz_scene() failed");
DvzFigure* figure = dvz_figure(scene, WIDTH, HEIGHT, 0);
DvzPanel* panel = figure != NULL ? dvz_panel_full(figure) : NULL;
DvzVisual* point = dvz_point(scene, 0);
EXAMPLE_CHECK(
figure != NULL && panel != NULL && point != NULL, "failed to create scene objects");
example_graphite_cyan_set_panel_background(panel);
vec3 positions[POINT_COUNT] = {
{-0.70f, -0.35f, 0.0f}, {-0.35f, +0.20f, 0.0f}, {+0.00f, -0.10f, 0.0f},
{+0.35f, +0.35f, 0.0f}, {+0.70f, -0.20f, 0.0f},
};
GuiControlsState state = {
.point = point,
.diameter_px = 42.0f,
.color = {0.28f, 0.78f, 1.00f, 1.00f},
.visible = true,
.pulse = true,
.palette = 0,
.glyph_count = 96,
.opacity = 1.0f,
.jitter = {0.12f, -0.08f},
.contrast = {0.08f, 0.32f, 0.72f, 0.94f},
.bloom_enabled = true,
.bloom_radius = 3.0f,
.bloom_threshold = 0.62f,
.contour_enabled = false,
.contour_width = 1.4f,
.contour_range = {0.18f, 0.82f},
.diagnostic_overlay = false,
.show_histogram = true,
.clip_min = {0.05f, 0.05f, 0.05f},
.clip_max = {0.95f, 0.95f, 0.95f},
.light_direction = {-0.35f, +0.55f, 0.75f},
};
DvzVisualDataUpdate updates[] = {
{.attr_name = "position", .data = positions, .item_count = POINT_COUNT},
};
int rc = dvz_visual_set_data_many(point, updates, 1);
EXAMPLE_CHECK(rc == 0 && _gui_controls_upload(&state), "failed to upload point data");
DvzPointStyleDesc style = dvz_point_style_desc();
style.aspect = DVZ_SHAPE_ASPECT_FILLED;
style.stroke_width_px = 0.0f;
EXAMPLE_CHECK(dvz_point_set_style(point, &style) == 0, "dvz_point_set_style() failed");
EXAMPLE_CHECK(
dvz_visual_set_depth_test(point, false) == 0, "dvz_visual_set_depth_test() failed");
EXAMPLE_CHECK(dvz_panel_add_visual(panel, point, NULL) == 0, "dvz_panel_add_visual() failed");
app = dvz_app(scene);
EXAMPLE_CHECK(app != NULL, "dvz_app() failed (no GPU or display?)");
DvzView* view = dvz_view_window(app, figure, WIDTH, HEIGHT, "gui_controls");
EXAMPLE_CHECK(view != NULL, "dvz_view_window() failed (GLFW unavailable?)");
DvzGui* gui = dvz_view_gui(view, NULL);
EXAMPLE_CHECK(gui != NULL, "dvz_view_gui() failed");
dvz_view_set_gui_callback(view, _gui_controls_callback, &state);
if (example_png_capture_requested(argc, argv))
{
DvzAppCaptureConfig capture = {0};
EXAMPLE_CHECK(
example_png_capture_config("feature_gui_controls", &capture),
"failed to configure PNG capture");
EXAMPLE_CHECK(
example_run_with_capture(
app, view, example_frame_count_any_or_default(argc, argv, 4), &capture),
"PNG capture failed");
}
else
{
dvz_app_run(app, example_frame_count(argc, argv));
}
ret = 0;
cleanup:
if (app != NULL)
dvz_app_destroy(app);
if (scene != NULL)
dvz_scene_destroy(scene);
return ret;
}
Example details
- ID:
features_gui_controls - Category:
feature - Lane:
features - Status:
supported - Source:
examples/c/features/gui_controls.c - Approved adaptation starter:
yes - Python source:
examples/python/gallery/features/gui_controls.py - Python adaptation: Available; direct-engine adaptation
- Browser support: Native only
- Browser note: Datoviz GUI controls require the native ImGui/GLFW app path
- Browser capability tags:
gui - Validation:
smoke+interaction+screenshot
Data
| Field | Value |
|---|---|
kind |
synthetic |