Skip to content

Graph Composite

This example builds a small graph composite.

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

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

Node arrays provide positions, semantic IDs, community colors, and sizes; edge arrays provide endpoints, IDs, colors, widths, and optional Bezier controls. Compare node clusters, thicker bridge edges, and curved links to see how a graph composite can turn relationship tables into a readable scientific network.

Source

#!/usr/bin/env python3
"""Semantic graph composite with community-colored nodes and Bezier edges."""

from __future__ import annotations

import ctypes

import numpy as np

import datoviz as dvz

from examples.python.gallery import common as ex


COMMUNITIES = (
    dvz.DvzColor(65, 201, 226, 255),
    dvz.DvzColor(246, 185, 72, 255),
    dvz.DvzColor(234, 104, 91, 255),
)

NODES = (
    ("V1", 0, (-1.05, +0.42, 0.0), 101, 0.88),
    ("V2", 0, (-0.80, +0.62, 0.0), 102, 0.72),
    ("V4", 0, (-0.55, +0.42, 0.0), 103, 0.76),
    ("LGN", 0, (-0.92, +0.12, 0.0), 104, 0.66),
    ("MT", 0, (-0.62, +0.12, 0.0), 105, 0.70),
    ("M1", 1, (+0.55, +0.45, 0.0), 201, 0.86),
    ("S1", 1, (+0.82, +0.62, 0.0), 202, 0.78),
    ("SMA", 1, (+1.05, +0.36, 0.0), 203, 0.70),
    ("PMd", 1, (+0.70, +0.15, 0.0), 204, 0.67),
    ("Thal", 1, (+1.00, +0.08, 0.0), 205, 0.80),
    ("HPC-L", 2, (-0.30, -0.55, 0.0), 301, 0.84),
    ("HPC-R", 2, (+0.00, -0.78, 0.0), 302, 0.82),
    ("Ent", 2, (-0.58, -0.70, 0.0), 303, 0.63),
    ("PCC", 2, (+0.34, -0.50, 0.0), 304, 0.74),
    ("Amy", 2, (+0.55, -0.72, 0.0), 305, 0.60),
)

EDGES = (
    (0, 1, 1001, 0.84, False),
    (1, 2, 1002, 0.76, False),
    (0, 3, 1003, 0.62, False),
    (3, 4, 1004, 0.58, False),
    (2, 4, 1005, 0.71, False),
    (0, 4, 1006, 0.66, False),
    (5, 6, 2001, 0.82, False),
    (6, 7, 2002, 0.70, False),
    (5, 8, 2003, 0.64, False),
    (8, 9, 2004, 0.68, False),
    (7, 9, 2005, 0.74, False),
    (5, 9, 2006, 0.60, False),
    (10, 11, 3001, 0.86, False),
    (10, 12, 3002, 0.72, False),
    (11, 13, 3003, 0.66, False),
    (12, 14, 3004, 0.57, False),
    (13, 14, 3005, 0.61, False),
    (10, 13, 3006, 0.64, False),
    (2, 5, 4001, 0.48, True),
    (4, 8, 4002, 0.42, True),
    (1, 6, 4003, 0.35, True),
    (3, 12, 4004, 0.44, True),
    (4, 10, 4005, 0.39, True),
    (8, 13, 4006, 0.50, True),
    (9, 14, 4007, 0.46, True),
    (5, 13, 4008, 0.37, True),
)


def _positions() -> np.ndarray:
    return np.array([node[2] for node in NODES], dtype=np.float64)


def _edge_endpoints() -> np.ndarray:
    return np.array([(edge[0], edge[1]) for edge in EDGES], dtype=np.uint32).reshape(-1)


def _default_edge_controls(p0: np.ndarray, p3: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
    delta = p3 - p0
    length = float(np.hypot(delta[0], delta[1]))
    bend = 0.18 * length if length > 0.0 else 0.0
    normal = np.array((-delta[1] / length, delta[0] / length, 0.0)) if length > 0.0 else 0.0
    c0 = p0 + delta / 3.0 + normal * bend
    c1 = p0 + 2.0 * delta / 3.0 + normal * bend
    return c0, c1


def _bridge_edge_controls(edge_index: int, bridge_index: int, positions: np.ndarray):
    source, target, _semantic_id, weight, _bridge = EDGES[edge_index]
    p0 = positions[source]
    p3 = positions[target]
    delta = p3 - p0
    bend = (1.0 if bridge_index % 2 == 0 else -1.0) * (0.14 + 0.08 * weight)
    offset = np.array((-bend * delta[1], bend * delta[0], 0.0), dtype=np.float64)
    c0 = p0 + 0.36 * delta + offset
    c1 = p0 + 0.64 * delta + offset
    return c0, c1


def _add_cubic_bounds(bounds: list[float], p0, c0, c1, p3) -> None:
    for t in np.linspace(0.0, 1.0, 33):
        u = 1.0 - t
        point = u * u * u * p0 + 3.0 * u * u * t * c0 + 3.0 * u * t * t * c1 + t * t * t * p3
        bounds[0] = min(bounds[0], float(point[0]))
        bounds[1] = max(bounds[1], float(point[0]))
        bounds[2] = min(bounds[2], float(point[1]))
        bounds[3] = max(bounds[3], float(point[1]))


def _graph_bounds(positions: np.ndarray) -> tuple[float, float, float, float]:
    bounds = [
        float(np.min(positions[:, 0])),
        float(np.max(positions[:, 0])),
        float(np.min(positions[:, 1])),
        float(np.max(positions[:, 1])),
    ]
    bridge_index = 0
    for edge_index, (source, target, _semantic_id, _weight, bridge) in enumerate(EDGES):
        p0 = positions[source]
        p3 = positions[target]
        if bridge:
            c0, c1 = _bridge_edge_controls(edge_index, bridge_index, positions)
            bridge_index += 1
        else:
            c0, c1 = _default_edge_controls(p0, p3)
        _add_cubic_bounds(bounds, p0, c0, c1, p3)
    return bounds[0], bounds[1], bounds[2], bounds[3]


def _set_equal_view2d(panel, positions: np.ndarray) -> None:
    padding = dvz.DvzPanelReserve()
    padding.left_px = 24.0
    padding.right_px = 24.0
    padding.top_px = 18.0
    padding.bottom_px = 18.0
    if dvz.dvz_panel_set_padding(panel, ctypes.byref(padding)) != 0:
        raise RuntimeError("dvz_panel_set_padding() failed")

    xmin, xmax, ymin, ymax = _graph_bounds(positions)
    desc = dvz.dvz_panel_view2d_desc()
    desc.mode = dvz.DVZ_PANEL_VIEW2D_CONTAIN
    desc.aspect = dvz.DVZ_PANEL_VIEW2D_ASPECT_EQUAL
    desc.padding = 0.14
    desc.domain_x[:] = (xmin, xmax)
    desc.domain_y[:] = (ymin, ymax)
    desc.has_domain_x = True
    desc.has_domain_y = True
    if dvz.dvz_panel_set_view2d(panel, ctypes.byref(desc)) != 0:
        raise RuntimeError("dvz_panel_set_view2d() failed")


def _color_array(colors: list[dvz.DvzColor]):
    array_type = dvz.DvzColor * len(colors)
    return array_type(*colors)


def _float_array(values):
    array_type = ctypes.c_float * len(values)
    return array_type(*(float(value) for value in values))


def _uint64_array(values: list[int]):
    array_type = ctypes.c_uint64 * len(values)
    return array_type(*values)


def _add_graph(scene, panel, positions: np.ndarray) -> None:
    graph = dvz.dvz_graph(scene, 0)
    if not graph:
        raise RuntimeError("dvz_graph() failed")

    endpoints = _edge_endpoints()
    if dvz.dvz_graph_set_node_count(graph, len(NODES)) != 0:
        raise RuntimeError("dvz_graph_set_node_count() failed")
    if (
        dvz.dvz_graph_set_node_positions(
            graph, 0, len(NODES), ctypes.c_void_p(positions.ctypes.data)
        )
        != 0
    ):
        raise RuntimeError("dvz_graph_set_node_positions() failed")
    if dvz.dvz_graph_set_edge_count(graph, len(EDGES)) != 0:
        raise RuntimeError("dvz_graph_set_edge_count() failed")
    if (
        dvz.dvz_graph_set_edge_endpoints(
            graph, 0, len(EDGES), endpoints.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32))
        )
        != 0
    ):
        raise RuntimeError("dvz_graph_set_edge_endpoints() failed")

    node_ids = _uint64_array([node[3] for node in NODES])
    edge_ids = _uint64_array([edge[2] for edge in EDGES])
    if dvz.dvz_graph_set_node_ids(graph, 0, len(NODES), node_ids) != 0:
        raise RuntimeError("dvz_graph_set_node_ids() failed")
    if dvz.dvz_graph_set_edge_ids(graph, 0, len(EDGES), edge_ids) != 0:
        raise RuntimeError("dvz_graph_set_edge_ids() failed")

    node_colors = _color_array([COMMUNITIES[node[1]] for node in NODES])
    node_sizes = np.array([18.0 + 24.0 * node[4] for node in NODES], dtype=np.float32)
    if dvz.dvz_graph_set_node_colors(graph, 0, len(NODES), node_colors) != 0:
        raise RuntimeError("dvz_graph_set_node_colors() failed")
    if dvz.dvz_graph_set_node_sizes(graph, 0, len(NODES), _float_array(node_sizes)) != 0:
        raise RuntimeError("dvz_graph_set_node_sizes() failed")

    edge_colors = []
    edge_widths = np.empty(len(EDGES), dtype=np.float32)
    for i, (source, _target, _semantic_id, weight, bridge) in enumerate(EDGES):
        if bridge:
            edge_colors.append(dvz.DvzColor(222, 236, 244, 185))
        else:
            community = NODES[source][1]
            color = COMMUNITIES[community]
            edge_colors.append(dvz.DvzColor(color.r, color.g, color.b, 105))
        edge_widths[i] = 1.1 + 4.2 * weight

    if dvz.dvz_graph_set_edge_colors(graph, 0, len(EDGES), _color_array(edge_colors)) != 0:
        raise RuntimeError("dvz_graph_set_edge_colors() failed")
    if dvz.dvz_graph_set_edge_widths(graph, 0, len(EDGES), _float_array(edge_widths)) != 0:
        raise RuntimeError("dvz_graph_set_edge_widths() failed")

    style = dvz.dvz_graph_edge_style()
    style.mode = dvz.DVZ_GRAPH_EDGE_MODE_BEZIER
    style.tessellation_segment_count = 22
    if dvz.dvz_graph_set_edge_style(graph, ctypes.byref(style)) != 0:
        raise RuntimeError("dvz_graph_set_edge_style() failed")

    bridge_first_edge = next((i for i, edge in enumerate(EDGES) if edge[4]), len(EDGES))
    bridge_count = len(EDGES) - bridge_first_edge
    if bridge_count:
        controls0 = np.empty((bridge_count, 3), dtype=np.float64)
        controls1 = np.empty((bridge_count, 3), dtype=np.float64)
        for bridge_index in range(bridge_count):
            edge_index = bridge_first_edge + bridge_index
            controls0[bridge_index], controls1[bridge_index] = _bridge_edge_controls(
                edge_index, bridge_index, positions
            )
        if (
            dvz.dvz_graph_set_edge_controls(
                graph,
                bridge_first_edge,
                bridge_count,
                ctypes.c_void_p(controls0.ctypes.data),
                ctypes.c_void_p(controls1.ctypes.data),
            )
            != 0
        ):
            raise RuntimeError("dvz_graph_set_edge_controls() failed")

    composite = dvz.dvz_graph_composite(graph, 0)
    if not composite:
        raise RuntimeError("dvz_graph_composite() failed")
    attach = dvz.dvz_visual_attach_desc()
    attach.coord_space = dvz.DVZ_VISUAL_COORD_DATA
    if dvz.dvz_panel_add_composite(panel, composite, ctypes.byref(attach)) != 0:
        raise RuntimeError("dvz_panel_add_composite() failed")


def _build_scene():
    scene, figure, panel = ex.scene_panel()
    positions = _positions()
    _set_equal_view2d(panel, positions)
    _add_graph(scene, panel, positions)
    return scene, figure, panel


def _configure_view(view, scene, panel) -> None:
    ex.bind_panzoom(view, scene, panel, dvz.DVZ_DIM_MASK_XY)


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

    def configure(view) -> None:
        _configure_view(view, scene, panel)

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

/* graph - This example builds a small graph composite.
 *
 * What to look for: node arrays provide positions, semantic IDs, community colors, and sizes;
 * edge arrays provide endpoints, IDs, colors, widths, and optional Bezier controls. Compare node
 * clusters, thicker bridge edges, and curved links to see how a graph composite can turn
 * relationship tables into a readable scientific network.
 *
 * Scenario: composites_graph
 * Style: feature composite, graphite_cyan, 1280x720 window target
 *
 * Build:  just example-c composites/graph
 * Run:    ./build/examples/c/composites/graph --live
 * Smoke:  ./build/examples/c/composites/graph --png
 */



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

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

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



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

#define WIDTH  EXAMPLE_WINDOW_WIDTH
#define HEIGHT EXAMPLE_WINDOW_HEIGHT

DvzScenarioSpec dvz_composite_graph_scenario(void);



/*************************************************************************************************/
/*  Structs                                                                                      */
/*************************************************************************************************/

typedef struct Community
{
    DvzColor color;
} Community;


typedef struct Node
{
    const char* label;
    uint32_t community;
    dvec3 position;
    uint64_t semantic_id;
    float strength;
} Node;


typedef struct Edge
{
    uint32_t source;
    uint32_t target;
    uint64_t semantic_id;
    float weight;
    bool bridge;
} Edge;



/*************************************************************************************************/
/*  Data                                                                                         */
/*************************************************************************************************/

static const Community COMMUNITIES[] = {
    {.color = {65, 201, 226, 255}},
    {.color = {246, 185, 72, 255}},
    {.color = {234, 104, 91, 255}},
};


static const Node NODES[] = {
    {.label = "V1", .community = 0, .position = {-1.05, +0.42, 0.0}, .semantic_id = 101, .strength = 0.88f},
    {.label = "V2", .community = 0, .position = {-0.80, +0.62, 0.0}, .semantic_id = 102, .strength = 0.72f},
    {.label = "V4", .community = 0, .position = {-0.55, +0.42, 0.0}, .semantic_id = 103, .strength = 0.76f},
    {.label = "LGN", .community = 0, .position = {-0.92, +0.12, 0.0}, .semantic_id = 104, .strength = 0.66f},
    {.label = "MT", .community = 0, .position = {-0.62, +0.12, 0.0}, .semantic_id = 105, .strength = 0.70f},
    {.label = "M1", .community = 1, .position = {+0.55, +0.45, 0.0}, .semantic_id = 201, .strength = 0.86f},
    {.label = "S1", .community = 1, .position = {+0.82, +0.62, 0.0}, .semantic_id = 202, .strength = 0.78f},
    {.label = "SMA", .community = 1, .position = {+1.05, +0.36, 0.0}, .semantic_id = 203, .strength = 0.70f},
    {.label = "PMd", .community = 1, .position = {+0.70, +0.15, 0.0}, .semantic_id = 204, .strength = 0.67f},
    {.label = "Thal", .community = 1, .position = {+1.00, +0.08, 0.0}, .semantic_id = 205, .strength = 0.80f},
    {.label = "HPC-L", .community = 2, .position = {-0.30, -0.55, 0.0}, .semantic_id = 301, .strength = 0.84f},
    {.label = "HPC-R", .community = 2, .position = {+0.00, -0.78, 0.0}, .semantic_id = 302, .strength = 0.82f},
    {.label = "Ent", .community = 2, .position = {-0.58, -0.70, 0.0}, .semantic_id = 303, .strength = 0.63f},
    {.label = "PCC", .community = 2, .position = {+0.34, -0.50, 0.0}, .semantic_id = 304, .strength = 0.74f},
    {.label = "Amy", .community = 2, .position = {+0.55, -0.72, 0.0}, .semantic_id = 305, .strength = 0.60f},
};


static const Edge EDGES[] = {
    {.source = 0, .target = 1, .semantic_id = 1001, .weight = 0.84f, .bridge = false},
    {.source = 1, .target = 2, .semantic_id = 1002, .weight = 0.76f, .bridge = false},
    {.source = 0, .target = 3, .semantic_id = 1003, .weight = 0.62f, .bridge = false},
    {.source = 3, .target = 4, .semantic_id = 1004, .weight = 0.58f, .bridge = false},
    {.source = 2, .target = 4, .semantic_id = 1005, .weight = 0.71f, .bridge = false},
    {.source = 0, .target = 4, .semantic_id = 1006, .weight = 0.66f, .bridge = false},
    {.source = 5, .target = 6, .semantic_id = 2001, .weight = 0.82f, .bridge = false},
    {.source = 6, .target = 7, .semantic_id = 2002, .weight = 0.70f, .bridge = false},
    {.source = 5, .target = 8, .semantic_id = 2003, .weight = 0.64f, .bridge = false},
    {.source = 8, .target = 9, .semantic_id = 2004, .weight = 0.68f, .bridge = false},
    {.source = 7, .target = 9, .semantic_id = 2005, .weight = 0.74f, .bridge = false},
    {.source = 5, .target = 9, .semantic_id = 2006, .weight = 0.60f, .bridge = false},
    {.source = 10, .target = 11, .semantic_id = 3001, .weight = 0.86f, .bridge = false},
    {.source = 10, .target = 12, .semantic_id = 3002, .weight = 0.72f, .bridge = false},
    {.source = 11, .target = 13, .semantic_id = 3003, .weight = 0.66f, .bridge = false},
    {.source = 12, .target = 14, .semantic_id = 3004, .weight = 0.57f, .bridge = false},
    {.source = 13, .target = 14, .semantic_id = 3005, .weight = 0.61f, .bridge = false},
    {.source = 10, .target = 13, .semantic_id = 3006, .weight = 0.64f, .bridge = false},
    {.source = 2, .target = 5, .semantic_id = 4001, .weight = 0.48f, .bridge = true},
    {.source = 4, .target = 8, .semantic_id = 4002, .weight = 0.42f, .bridge = true},
    {.source = 1, .target = 6, .semantic_id = 4003, .weight = 0.35f, .bridge = true},
    {.source = 3, .target = 12, .semantic_id = 4004, .weight = 0.44f, .bridge = true},
    {.source = 4, .target = 10, .semantic_id = 4005, .weight = 0.39f, .bridge = true},
    {.source = 8, .target = 13, .semantic_id = 4006, .weight = 0.50f, .bridge = true},
    {.source = 9, .target = 14, .semantic_id = 4007, .weight = 0.46f, .bridge = true},
    {.source = 5, .target = 13, .semantic_id = 4008, .weight = 0.37f, .bridge = true},
};


typedef struct Bounds
{
    double xmin;
    double xmax;
    double ymin;
    double ymax;
} Bounds;


enum
{
    COMMUNITY_COUNT = sizeof(COMMUNITIES) / sizeof(COMMUNITIES[0]),
    NODE_COUNT = sizeof(NODES) / sizeof(NODES[0]),
    EDGE_COUNT = sizeof(EDGES) / sizeof(EDGES[0]),
};



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

static void _bounds_add(Bounds* bounds, double x, double y)
{
    ANN(bounds);
    if (x < bounds->xmin)
        bounds->xmin = x;
    if (x > bounds->xmax)
        bounds->xmax = x;
    if (y < bounds->ymin)
        bounds->ymin = y;
    if (y > bounds->ymax)
        bounds->ymax = y;
}



static void _default_edge_controls(const dvec3 p0, const dvec3 p3, dvec3 c0, dvec3 c1)
{
    ANN(p0);
    ANN(p3);
    ANN(c0);
    ANN(c1);

    const double dx = p3[0] - p0[0];
    const double dy = p3[1] - p0[1];
    const double dz = p3[2] - p0[2];
    const double length = sqrt(dx * dx + dy * dy);
    const double bend = length > 0 ? 0.18 * length : 0.0;
    const double nx = length > 0 ? -dy / length : 0.0;
    const double ny = length > 0 ? dx / length : 0.0;

    c0[0] = p0[0] + dx / 3.0 + nx * bend;
    c0[1] = p0[1] + dy / 3.0 + ny * bend;
    c0[2] = p0[2] + dz / 3.0;
    c1[0] = p0[0] + 2.0 * dx / 3.0 + nx * bend;
    c1[1] = p0[1] + 2.0 * dy / 3.0 + ny * bend;
    c1[2] = p0[2] + 2.0 * dz / 3.0;
}



static void _bridge_edge_controls(uint32_t edge_index, uint32_t bridge_index, dvec3 c0, dvec3 c1)
{
    ASSERT(edge_index < EDGE_COUNT);
    ANN(c0);
    ANN(c1);

    const Edge* edge = &EDGES[edge_index];
    const dvec3* source = &NODES[edge->source].position;
    const dvec3* target = &NODES[edge->target].position;
    const double sx = (*source)[0];
    const double sy = (*source)[1];
    const double tx = (*target)[0];
    const double ty = (*target)[1];
    const double dx = tx - sx;
    const double dy = ty - sy;
    const double bend = (bridge_index % 2 == 0 ? 1.0 : -1.0) * (0.14 + 0.08 * edge->weight);

    c0[0] = sx + 0.36 * dx - bend * dy;
    c0[1] = sy + 0.36 * dy + bend * dx;
    c0[2] = 0.0;
    c1[0] = sx + 0.64 * dx - bend * dy;
    c1[1] = sy + 0.64 * dy + bend * dx;
    c1[2] = 0.0;
}



static void _bounds_add_cubic(
    Bounds* bounds, const dvec3 p0, const dvec3 c0, const dvec3 c1, const dvec3 p3)
{
    ANN(bounds);
    ANN(p0);
    ANN(c0);
    ANN(c1);
    ANN(p3);

    for (uint32_t i = 0; i <= 32; i++)
    {
        const double t = (double)i / 32.0;
        const double u = 1.0 - t;
        const double uu = u * u;
        const double tt = t * t;
        const double x =
            uu * u * p0[0] + 3.0 * uu * t * c0[0] + 3.0 * u * tt * c1[0] + tt * t * p3[0];
        const double y =
            uu * u * p0[1] + 3.0 * uu * t * c0[1] + 3.0 * u * tt * c1[1] + tt * t * p3[1];
        _bounds_add(bounds, x, y);
    }
}



static Bounds _graph_bounds(void)
{
    Bounds bounds = {.xmin = DBL_MAX, .xmax = -DBL_MAX, .ymin = DBL_MAX, .ymax = -DBL_MAX};
    for (uint32_t i = 0; i < NODE_COUNT; i++)
        _bounds_add(&bounds, NODES[i].position[0], NODES[i].position[1]);

    uint32_t bridge_index = 0;
    for (uint32_t i = 0; i < EDGE_COUNT; i++)
    {
        const Edge* edge = &EDGES[i];
        const dvec3* p0 = &NODES[edge->source].position;
        const dvec3* p3 = &NODES[edge->target].position;
        dvec3 c0 = {0};
        dvec3 c1 = {0};
        if (edge->bridge)
            _bridge_edge_controls(i, bridge_index++, c0, c1);
        else
            _default_edge_controls(*p0, *p3, c0, c1);
        _bounds_add_cubic(&bounds, *p0, c0, c1, *p3);
    }
    return bounds;
}



/**
 * Configure the panel used by the graph composite example.
 *
 * @param panel target panel
 * @return whether setup succeeded
 */
static bool _configure_panel(DvzPanel* panel)
{
    ANN(panel);
    const Bounds bounds = _graph_bounds();
    if (dvz_panel_set_padding(
            panel, &(DvzPanelReserve){
                       .left_px = 24.0f, .right_px = 24.0f, .bottom_px = 18.0f,
                       .top_px = 18.0f}) != DVZ_OK)
        return false;
    return example_configure_equal_aspect_panel(
        panel, (DvzDataDomain){.min = bounds.xmin, .max = bounds.xmax},
        (DvzDataDomain){.min = bounds.ymin, .max = bounds.ymax}, 0.14);
}



/**
 * Fill node positions from the fixed -region table.
 *
 * @param positions output node positions
 */
static void _make_positions(dvec3 positions[NODE_COUNT])
{
    ANN(positions);
    for (uint32_t i = 0; i < NODE_COUNT; i++)
    {
        positions[i][0] = NODES[i].position[0];
        positions[i][1] = NODES[i].position[1];
        positions[i][2] = NODES[i].position[2];
    }
}



static void _push_edge(uint32_t endpoints[2 * EDGE_COUNT], uint32_t* edge_count, uint32_t source,
                       uint32_t target)
{
    ANN(endpoints);
    ANN(edge_count);
    ASSERT(*edge_count < EDGE_COUNT);
    endpoints[2 * *edge_count + 0] = source;
    endpoints[2 * *edge_count + 1] = target;
    *edge_count += 1;
}



static void _make_edges(uint32_t endpoints[2 * EDGE_COUNT], uint32_t* bridge_first_edge)
{
    ANN(endpoints);
    ANN(bridge_first_edge);

    uint32_t edge_count = 0;
    *bridge_first_edge = EDGE_COUNT;
    for (uint32_t i = 0; i < EDGE_COUNT; i++)
    {
        if (EDGES[i].bridge && *bridge_first_edge == EDGE_COUNT)
            *bridge_first_edge = i;
        ASSERT(!EDGES[i].bridge || i >= *bridge_first_edge);
        _push_edge(endpoints, &edge_count, EDGES[i].source, EDGES[i].target);
    }

    ASSERT(edge_count == EDGE_COUNT);
}



static void _make_bridge_controls(
    dvec3 positions[NODE_COUNT], const uint32_t endpoints[2 * EDGE_COUNT],
    uint32_t bridge_first_edge, uint32_t bridge_count, dvec3 control0[EDGE_COUNT],
    dvec3 control1[EDGE_COUNT])
{
    ANN(positions);
    ANN(endpoints);
    ANN(control0);
    ANN(control1);

    for (uint32_t i = 0; i < bridge_count; i++)
    {
        const uint32_t edge_index = bridge_first_edge + i;
        ASSERT(endpoints[2 * edge_index + 0] == EDGES[edge_index].source);
        ASSERT(endpoints[2 * edge_index + 1] == EDGES[edge_index].target);
        _bridge_edge_controls(edge_index, i, control0[i], control1[i]);
    }
}



/**
 * Add a semantic graph composite.
 *
 * @param scene scene owning the graph
 * @param panel panel receiving the composite
 * @return whether the graph was created and attached
 */
static bool _add_graph(DvzScene* scene, DvzPanel* panel)
{
    ANN(scene);
    ANN(panel);

    dvec3 positions[NODE_COUNT] = {0};
    _make_positions(positions);

    uint32_t edges[2 * EDGE_COUNT] = {0};
    uint32_t bridge_first_edge = 0;
    _make_edges(edges, &bridge_first_edge);

    DvzGraph* graph = dvz_graph(scene, 0);
    if (graph == NULL)
        return false;
    int rc = dvz_graph_set_node_count(graph, NODE_COUNT);
    if (rc != 0)
        return false;
    rc = dvz_graph_set_node_positions(graph, 0, NODE_COUNT, (const dvec3*)positions);
    if (rc != 0)
        return false;
    rc = dvz_graph_set_edge_count(graph, EDGE_COUNT);
    if (rc != 0)
        return false;
    rc = dvz_graph_set_edge_endpoints(graph, 0, EDGE_COUNT, edges);
    if (rc != 0)
        return false;
    uint64_t node_ids[NODE_COUNT] = {0};
    uint64_t edge_ids[EDGE_COUNT] = {0};
    for (uint32_t i = 0; i < NODE_COUNT; i++)
        node_ids[i] = NODES[i].semantic_id;
    for (uint32_t i = 0; i < EDGE_COUNT; i++)
        edge_ids[i] = EDGES[i].semantic_id;
    rc = dvz_graph_set_node_ids(graph, 0, NODE_COUNT, node_ids);
    if (rc != 0)
        return false;
    rc = dvz_graph_set_edge_ids(graph, 0, EDGE_COUNT, edge_ids);
    if (rc != 0)
        return false;

    DvzColor node_colors[NODE_COUNT] = {0};
    float node_sizes[NODE_COUNT] = {0};
    for (uint32_t i = 0; i < NODE_COUNT; i++)
    {
        const uint32_t community = NODES[i].community;
        ASSERT(community < COMMUNITY_COUNT);
        node_colors[i] = COMMUNITIES[community].color;
        node_sizes[i] = 18.0f + 24.0f * NODES[i].strength;
    }
    rc = dvz_graph_set_node_colors(graph, 0, NODE_COUNT, node_colors);
    if (rc != 0)
        return false;
    rc = dvz_graph_set_node_sizes(graph, 0, NODE_COUNT, node_sizes);
    if (rc != 0)
        return false;

    DvzColor edge_colors[EDGE_COUNT] = {0};
    float edge_widths[EDGE_COUNT] = {0};
    for (uint32_t i = 0; i < EDGE_COUNT; i++)
    {
        const bool bridge = EDGES[i].bridge;
        const uint32_t community = NODES[EDGES[i].source].community;
        edge_colors[i] = bridge ? (DvzColor){222, 236, 244, 180} : COMMUNITIES[community].color;
        edge_colors[i].a = bridge ? 185u : 105u;
        edge_widths[i] = 1.1f + 4.2f * EDGES[i].weight;
    }
    rc = dvz_graph_set_edge_colors(graph, 0, EDGE_COUNT, edge_colors);
    if (rc != 0)
        return false;
    rc = dvz_graph_set_edge_widths(graph, 0, EDGE_COUNT, edge_widths);
    if (rc != 0)
        return false;

    DvzGraphEdgeStyle edge_style = dvz_graph_edge_style();
    edge_style.mode = DVZ_GRAPH_EDGE_MODE_BEZIER;
    edge_style.tessellation_segment_count = 22;
    rc = dvz_graph_set_edge_style(graph, &edge_style);
    if (rc != 0)
        return false;

    const uint32_t bridge_count = EDGE_COUNT - bridge_first_edge;
    if (bridge_count > 0)
    {
        dvec3 control0[EDGE_COUNT] = {0};
        dvec3 control1[EDGE_COUNT] = {0};
        _make_bridge_controls(positions, edges, bridge_first_edge, bridge_count, control0, control1);
        rc = dvz_graph_set_edge_controls(
            graph, bridge_first_edge, bridge_count, (const dvec3*)control0, (const dvec3*)control1);
        if (rc != 0)
            return false;
    }

    DvzComposite* composite = dvz_graph_composite(graph, 0);
    if (composite == NULL)
        return false;
    rc = dvz_panel_add_composite(
        panel, composite, &(DvzVisualAttachDesc){DVZ_STRUCT_INIT_FIELDS(DvzVisualAttachDesc),
                                                .coord_space = DVZ_VISUAL_COORD_DATA,
                                                .z_layer = 0});
    return rc == 0;
}



static bool _scenario_init(DvzScenarioContext* ctx, void** out_user)
{
    if (ctx == NULL)
        return false;
    if (out_user != NULL)
        *out_user = NULL;

    bool ok = false;
    ctx->figure = dvz_figure(ctx->scene, ctx->width, ctx->height, 0);
    EXAMPLE_CHECK(ctx->figure != NULL, "dvz_figure() failed");

    DvzPanel* panel = dvz_panel(ctx->figure, &(DvzPanelDesc){0.0f, 0.0f, 1.0f, 1.0f});
    EXAMPLE_CHECK(panel != NULL, "dvz_panel() failed");
    EXAMPLE_CHECK(_configure_panel(panel), "panel configuration failed");
    EXAMPLE_CHECK(_add_graph(ctx->scene, panel), "graph setup failed");

    DvzPanzoomDesc panzoom_desc = dvz_panzoom_desc();
    DvzPanzoom* panzoom = dvz_scenario_panzoom(ctx, panel, &panzoom_desc, DVZ_DIM_MASK_XY);
    EXAMPLE_CHECK(panzoom != NULL, "failed to create or bind panzoom controller");
    (void)panzoom;

    ok = true;
cleanup:
    return ok;
}



DvzScenarioSpec dvz_composite_graph_scenario(void)
{
    return (DvzScenarioSpec){
        .id = "composites_graph",
        .title = "Graph Composite",
        .width = WIDTH,
        .height = HEIGHT,
        .fps = 60.0,
        .requirements = DVZ_SCENARIO_REQ_CONTROLLER | DVZ_SCENARIO_REQ_PANZOOM,
        .init = _scenario_init,
    };
}



/*************************************************************************************************/
/*  Main                                                                                         */
/*************************************************************************************************/

#ifndef DVZ_EXAMPLE_NO_MAIN
int main(int argc, char** argv)
{
    DvzScenarioSpec spec = dvz_composite_graph_scenario();
    return dvz_scenario_run_native_cli(&spec, argc, argv) == 0 ? 0 : 1;
}
#endif
Example details

Tags

composite, graph, marker-nodes, bezier-edges, panzoom

Data

Field Value
kind synthetic