Skip to content

Bounds Overlay

This example shows diagnostic bounds overlays for 2D and 3D visuals.

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/bounds_overlay (build and run), or rerun ./build/examples/c/features/bounds_overlay
Python Available; direct-engine adaptation python3 -m examples.python.gallery.features.bounds_overlay
Browser Live WebGPU route Open live example

Use this example as capability or integration evidence, not as a minimal copy-paste template. Start from the nearest supported, copy-safe example and add this feature after verifying the linked API reference.

What To Look For

The left panel contains a point cloud with position, color, and diameter arrays, while the right panel contains sphere centers, colors, and radii. Bounds visibility is enabled on both panels so the overlay reveals the data extent used by layout and interaction. Compare how the 2D point ring and the 3D sphere cluster are boxed; this helps debug unexpected clipping, wrong coordinate ranges, or visuals that are attached to the wrong space.

Source

#!/usr/bin/env python3
"""Diagnostic bounds overlays for 2D points and 3D spheres."""

from __future__ import annotations

import numpy as np

import datoviz as dvz

from examples.python.gallery import common as ex


POINT_COUNT = 256
SPHERE_COUNT = 32
SPHERE_RADIUS_SCALE = 1.5


def _point_data():
    t = np.linspace(0.0, 1.0, POINT_COUNT, dtype=np.float32)
    angle = np.float32(28.274333882308138) * t
    radius = 0.08 + 0.82 * t
    positions = np.column_stack(
        (radius * np.cos(angle), radius * np.sin(angle), np.zeros(POINT_COUNT, dtype=np.float32))
    ).astype(np.float32)

    colors = np.empty((POINT_COUNT, 4), dtype=np.uint8)
    colors[:, 0] = (42.0 + 180.0 * t).astype(np.uint8)
    colors[:, 1] = (210.0 - 72.0 * t).astype(np.uint8)
    colors[:, 2] = (255.0 - 165.0 * t).astype(np.uint8)
    colors[:, 3] = 235
    diameters = (8.0 + 22.0 * t).astype(np.float32)
    return positions, colors, diameters


def _sphere_data():
    positions = np.zeros((SPHERE_COUNT, 3), dtype=np.float32)
    colors = np.zeros((SPHERE_COUNT, 4), dtype=np.uint8)
    radii = np.zeros(SPHERE_COUNT, dtype=np.float32)
    radius_classes = np.array([0.070, 0.105, 0.165], dtype=np.float32)

    for i in range(SPHERE_COUNT):
        ix = i % 4
        iy = (i // 4) % 4
        iz = i // 16
        jx = 0.035 * np.sin(1.7 * i)
        jy = 0.035 * np.cos(2.1 * i)
        jz = 0.055 * np.sin(0.9 * i)
        positions[i] = (-0.54 + 0.36 * ix + jx, -0.54 + 0.36 * iy + jy, -0.36 + 0.72 * iz + jz)

        t = i / (SPHERE_COUNT - 1)
        colors[i] = (
            int(230.0 - 120.0 * t),
            int(80.0 + 120.0 * t),
            int(120.0 + 100.0 * t),
            255,
        )
        radius_class = (i * 7 + iz) % 3
        radii[i] = SPHERE_RADIUS_SCALE * (
            radius_classes[radius_class] + 0.006 * np.sin(0.8 * i)
        )
    return positions, colors, radii


def _add_points(scene, panel) -> None:
    point = dvz.dvz_point(scene, 0)
    if not point:
        raise RuntimeError("dvz_point() failed")
    positions, colors, diameters = _point_data()
    if dvz.dvz_visual_set_data_many(
        point,
        {
            "position": positions,
            "color": colors,
            "diameter_px": diameters,
        },
    ) != 0:
        raise RuntimeError("dvz_visual_set_data_many(point) failed")
    ex.add_visual(panel, point)


def _add_spheres(scene, panel) -> None:
    sphere = dvz.dvz_sphere(scene, 0)
    if not sphere:
        raise RuntimeError("dvz_sphere() failed")
    if dvz.dvz_sphere_set_mode(sphere, dvz.DVZ_SPHERE_MODE_RAYCAST_IMPOSTOR) != 0:
        raise RuntimeError("dvz_sphere_set_mode() failed")
    positions, colors, radii = _sphere_data()
    if dvz.dvz_visual_set_data_many(
        sphere,
        {
            "position": positions,
            "color": colors,
            "radius": radii,
        },
    ) != 0:
        raise RuntimeError("dvz_visual_set_data_many(sphere) failed")
    ex.add_visual(panel, sphere)


def _build_scene():
    scene = dvz.dvz_scene()
    if not scene:
        raise RuntimeError("dvz_scene() failed")
    figure = dvz.dvz_figure(scene, ex.WIDTH, ex.HEIGHT, 0)
    if not figure:
        raise RuntimeError("dvz_figure() failed")

    panel_2d = ex.panel_rect(figure, 0.04, 0.08, 0.44, 0.84)
    panel_3d = ex.panel_rect(figure, 0.52, 0.08, 0.44, 0.84)
    ex.manual_camera(panel_3d)

    _add_points(scene, panel_2d)
    _add_spheres(scene, panel_3d)
    if dvz.dvz_panel_set_bounds_visible(panel_2d, True) != 0:
        raise RuntimeError("dvz_panel_set_bounds_visible(2d) failed")
    if dvz.dvz_panel_set_bounds_visible(panel_3d, True) != 0:
        raise RuntimeError("dvz_panel_set_bounds_visible(3d) failed")
    return scene, figure, panel_2d, panel_3d


def main() -> None:
    scene, figure, panel_2d, panel_3d = _build_scene()

    def configure(view) -> None:
        ex.bind_panzoom(view, scene, panel_2d, dvz.DVZ_DIM_MASK_XY)
        arcball = dvz.dvz_view_arcball(view, panel_3d, None)
        if not arcball:
            raise RuntimeError("dvz_view_arcball() failed")

    ex.run_with_view(scene, figure, "Bounds Overlay", 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
 */

/* bounds_overlay - This example shows diagnostic bounds overlays for 2D and 3D visuals.
 *
 * Scenario: features_bounds_overlay
 * Style: features, graphite_cyan, 1280x720 window target
 *
 * Build:  just example-c features/bounds_overlay
 * Run:    ./build/examples/c/features/bounds_overlay --live
 * Smoke:  ./build/examples/c/features/bounds_overlay --png
 *
 * What to look for: the left panel contains a point cloud with position, color, and diameter
 * arrays, while the right panel contains sphere centers, colors, and radii. Bounds visibility is
 * enabled on both panels so the overlay reveals the data extent used by layout and interaction.
 * Compare how the 2D point ring and the 3D sphere cluster are boxed; this helps debug unexpected
 * clipping, wrong coordinate ranges, or visuals that are attached to the wrong space.
 */



/*************************************************************************************************/
/*  Includes                                                                                     */
/*************************************************************************************************/

#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

#include "_assertions.h"
#include "datoviz/scene.h"
#include "example_common.h"
#include "example_style.h"
#include "runner/scenario_runner.h"



/*************************************************************************************************/
/*  Forward declarations                                                                         */
/*************************************************************************************************/

DvzScenarioSpec dvz_example_bounds_overlay_scenario(void);



/*************************************************************************************************/
/*  Constants                                                                                    */
/*************************************************************************************************/

#define WIDTH  EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT
#define POINT_COUNT  256u
#define SPHERE_COUNT 32u
#define SPHERE_RADIUS_SCALE 1.5f



/*************************************************************************************************/
/*  Helpers                                                                                      */
/*************************************************************************************************/

/**
 * Fill a deterministic 2D point cloud with varying sizes.
 *
 * @param positions output positions
 * @param colors output colors
 * @param sizes output point diameters
 */
static void _fill_points(
    vec3 positions[POINT_COUNT], DvzColor colors[POINT_COUNT], float sizes[POINT_COUNT])
{
    ANN(positions);
    ANN(colors);
    ANN(sizes);

    for (uint32_t i = 0; i < POINT_COUNT; i++)
    {
        const float t = (float)i / (float)(POINT_COUNT - 1u);
        const float angle = 28.274333882308138f * t;
        const float radius = 0.08f + 0.82f * t;
        positions[i][0] = radius * cosf(angle);
        positions[i][1] = radius * sinf(angle);
        positions[i][2] = 0.0f;

        colors[i] = dvz_color_rgba(
            (uint8_t)(42.0f + 180.0f * t), (uint8_t)(210.0f - 72.0f * t),
            (uint8_t)(255.0f - 165.0f * t), 235);
        sizes[i] = 8.0f + 22.0f * t;
    }
}



/**
 * Fill deterministic 3D sphere centers and radii.
 *
 * @param positions output centers
 * @param colors output colors
 * @param radii output radii
 */
static void _fill_spheres(
    vec3 positions[SPHERE_COUNT], DvzColor colors[SPHERE_COUNT], float radii[SPHERE_COUNT])
{
    ANN(positions);
    ANN(colors);
    ANN(radii);

    for (uint32_t i = 0; i < SPHERE_COUNT; i++)
    {
        const uint32_t ix = i % 4u;
        const uint32_t iy = (i / 4u) % 4u;
        const uint32_t iz = i / 16u;
        const float jx = 0.035f * sinf(1.7f * (float)i);
        const float jy = 0.035f * cosf(2.1f * (float)i);
        const float jz = 0.055f * sinf(0.9f * (float)i);
        positions[i][0] = -0.54f + 0.36f * (float)ix + jx;
        positions[i][1] = -0.54f + 0.36f * (float)iy + jy;
        positions[i][2] = -0.36f + 0.72f * (float)iz + jz;

        const float t = (float)i / (float)(SPHERE_COUNT - 1u);
        colors[i] = dvz_color_rgb(
            (uint8_t)(230.0f - 120.0f * t), (uint8_t)(80.0f + 120.0f * t),
            (uint8_t)(120.0f + 100.0f * t));
        const float radius_classes[3] = {0.070f, 0.105f, 0.165f};
        const uint32_t radius_class = (i * 7u + iz) % 3u;
        radii[i] =
            SPHERE_RADIUS_SCALE *
            (radius_classes[radius_class] + 0.006f * sinf(0.8f * (float)i));
    }
}



/**
 * Add the 2D point visual whose aggregate bounds are shown by the overlay.
 *
 * @param scene scene owning visuals
 * @param panel target panel
 * @return true on success
 */
static bool _add_points(DvzScene* scene, DvzPanel* panel)
{
    ANN(scene);
    ANN(panel);

    DvzVisual* points = dvz_point(scene, 0);
    if (points == NULL)
        return false;

    vec3 point_positions[POINT_COUNT] = {0};
    DvzColor point_colors[POINT_COUNT] = {0};
    float point_sizes[POINT_COUNT] = {0};
    _fill_points(point_positions, point_colors, point_sizes);

    DvzVisualDataUpdate updates[] = {
        {.attr_name = "position", .data = point_positions, .item_count = POINT_COUNT},
        {.attr_name = "color", .data = point_colors, .item_count = POINT_COUNT},
        {.attr_name = "diameter_px", .data = point_sizes, .item_count = POINT_COUNT},
    };
    if (dvz_visual_set_data_many(points, updates, 3) != 0)
        return false;
    return dvz_panel_add_visual(panel, points, NULL) == 0;
}



/**
 * Add the 3D sphere visual whose aggregate bounds are shown by the overlay.
 *
 * @param scene scene owning visuals
 * @param panel target panel
 * @return true on success
 */
static bool _add_spheres(DvzScene* scene, DvzPanel* panel)
{
    ANN(scene);
    ANN(panel);

    DvzVisual* spheres = dvz_sphere(scene, 0);
    if (spheres == NULL)
        return false;
    if (dvz_sphere_set_mode(spheres, DVZ_SPHERE_MODE_RAYCAST_IMPOSTOR) != 0)
        return false;

    vec3 sphere_positions[SPHERE_COUNT] = {0};
    DvzColor sphere_colors[SPHERE_COUNT] = {0};
    float sphere_radii[SPHERE_COUNT] = {0};
    _fill_spheres(sphere_positions, sphere_colors, sphere_radii);

    DvzVisualDataUpdate updates[] = {
        {.attr_name = "position", .data = sphere_positions, .item_count = SPHERE_COUNT},
        {.attr_name = "color", .data = sphere_colors, .item_count = SPHERE_COUNT},
        {.attr_name = "radius", .data = sphere_radii, .item_count = SPHERE_COUNT},
    };
    if (dvz_visual_set_data_many(spheres, updates, 3) != 0)
        return false;
    return dvz_panel_add_visual(panel, spheres, NULL) == 0;
}



/*************************************************************************************************/
/*  Scenario callbacks                                                                           */
/*************************************************************************************************/

/**
 * Initialize the bounds-overlay feature scenario.
 *
 * @param ctx scenario context
 * @param out_user unused scenario state output
 * @return true on success
 */
static bool _scenario_init(DvzScenarioContext* ctx, void** out_user)
{
    if (ctx == NULL)
        return false;
    if (out_user != NULL)
        *out_user = NULL;

    ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
    if (ctx->figure == NULL)
        return false;

    DvzPanel* panel_2d = dvz_panel(ctx->figure, &(DvzPanelDesc){0.04f, 0.08f, 0.44f, 0.84f});
    DvzPanel* panel_3d = dvz_panel(ctx->figure, &(DvzPanelDesc){0.52f, 0.08f, 0.44f, 0.84f});
    if (panel_2d == NULL || panel_3d == NULL)
        return false;
    example_graphite_cyan_set_panel_background(panel_2d);
    example_graphite_cyan_set_panel_background(panel_3d);

    if (example_set_default_3d_camera(panel_3d, 1.0f) == NULL)
        return false;

    if (!_add_points(ctx->scene, panel_2d))
        return false;
    if (!_add_spheres(ctx->scene, panel_3d))
        return false;
    if (dvz_panel_set_bounds_visible(panel_2d, true) != 0)
        return false;
    if (dvz_panel_set_bounds_visible(panel_3d, true) != 0)
        return false;

    DvzController* panzoom = dvz_panzoom(ctx->scene, NULL);
    if (panzoom == NULL)
        return false;
    if (dvz_scenario_bind_controller(ctx, panel_2d, panzoom, DVZ_DIM_MASK_XY) != 0)
        return false;

    DvzController* arcball = dvz_arcball(ctx->scene, NULL);
    if (arcball == NULL)
        return false;
    if (dvz_scenario_bind_controller(ctx, panel_3d, arcball, DVZ_DIM_MASK_XYZ) != 0)
        return false;
    return true;
}



/**
 * Return the bounds-overlay feature scenario specification.
 *
 * @return scenario specification
 */
DvzScenarioSpec dvz_example_bounds_overlay_scenario(void)
{
    return (DvzScenarioSpec){
        .id = "features_bounds_overlay",
        .title = "Bounds Overlay",
        .width = WIDTH,
        .height = HEIGHT,
        .fps = 60.0,
        .init = _scenario_init,
    };
}



/*************************************************************************************************/
/*  Functions                                                                                    */
/*************************************************************************************************/

/**
 * Run the bounds-overlay feature example through the native scenario runner.
 *
 * @param argc command-line argument count
 * @param argv command-line argument vector
 * @return process exit code
 */
#ifndef DVZ_EXAMPLE_NO_MAIN
int main(int argc, char** argv)
{
    DvzScenarioSpec spec = dvz_example_bounds_overlay_scenario();
    return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
#endif
Example details

Tags

diagnostic, bounds, overlay, point, sphere

Data

Field Value
kind synthetic