Skip to contents

g6R (development version)

New features

  • New g6_outline() plugin: a panel listing the graph as a tree, for when the drawing is too big to read (#65). Each combo is an accordion holding its members, in flow order rather than insertion order, and clicking a row moves the viewport to that element and selects it. It pairs with collapsing: with everything listed, the canvas no longer has to be legible at fit-to-view, so groups can stay collapsed and any element is still two clicks away. Collapsing a group on the canvas folds its rows in the panel, so the two agree about what is open (followCollapse = FALSE to keep them independent). It is one-way: folding a group in the panel leaves the canvas alone, so a group can be looked into without redrawing the graph, and collapsing or expanding it on the canvas takes that fold back over. Selecting an element on the canvas – by clicking it, or by finding it with g6_search() – unfolds the groups above it, marks its row and scrolls to it, so the panel follows where you are. A panel that was shut when the selection happened catches up when it is opened. anchor = "search" hangs it under [g6_search()]’s box as a dropdown instead of floating in its own corner, so the two read as one control (it needs the search plugin listed first, and falls back to a corner if there is none); position, width, open and groupsOpen place and reveal it otherwise. Rows are compact: no trailing type column, since the indentation already says which group a row is in and the glyph says what kind it is, and the gutter before the label is kept tight because in a panel this narrow it is width the labels do not get. Each group row reports how many elements it holds, counted through nested groups and reported for a folded group too, parked in the empty right-hand space so it costs the label nothing, and the toggle carries the same figure for the graph as a whole so its size is legible while the panel is shut. outputId reports the click as input$<outputId>-outlined_element. The list tracks the graph: adding, removing, renaming or reparenting an element rebuilds it, and the panel is what makes collapsing safe: the canvas can stay clean because everything is still listed and reachable here. Shares its row rendering, focus and expand-collapsed-ancestors logic with g6_search(), so the two cannot drift apart.

  • New g6_search() plugin: a search box over the canvas for navigating a graph too large to read at once (#65). Type to match nodes and combos by label or id, pick a hit with the mouse or the arrow keys plus Enter, and the viewport moves to it. G6 ships no search UI, so this is g6R’s first custom plugin, built on the focusElement() that g6_focus_nodes() already uses. Matching runs in the browser, so the box works in a plain widget (a Quarto document, a pkgdown page, a vignette) and not only under Shiny, and it does not wait on the server between keystrokes. A pick also selects the element by default, so anything driven by selection follows along, and outputId reports it to Shiny as input$<outputId>-searched_element. Picks do not pile up selections: each one deselects the previous, across both panels, so a search after an outline click (or the reverse) leaves one thing selected rather than two. A selection the user made by clicking is left alone. Results tell the two kinds apart by shape rather than by a word: a dot for a single element against a folder for a container, drawn rather than approximated with a styled box, since at this size a bordered box just reads as another node. Both are the one grey, so the shape carries the type and nothing else has to. A node also shows the group it lives in, which is what distinguishes two similarly named blocks, and combos are listed before nodes so the kinds never interleave. labels renames the types for apps that do not call them nodes and combos (labels = c(node = "block", combo = "stack")), since “combo” is g6 jargon. Collapsed groups are opened on the way to a match (expandAncestors, on by default): a node inside a collapsed combo has nowhere on screen to focus, and collapsing is exactly what keeps a large graph readable, so the two belong together. See inst/examples/search for a 151-node workflow of 10 branching groups, the shape that makes a graph hard to navigate.

Bug fixes

  • Dragging a combo no longer drags nodes out of other combos (#63). G6 translates a dragged combo by walking its descendants through getChildrenData(), which selects the hierarchy from the element’s type: the first hop follows the combo hierarchy, but every hop after that starts at a node and switches to the tree hierarchy, so the walk escaped the combo and dragged the transitive children closure of its members. Any node in that closure belonging to another combo was pulled out of it, leaving that combo’s box behind and apparently empty. drag-element is now overridden to expand the dragged combo through the combo hierarchy itself and translate the member nodes, which walk no hierarchy at all; a combo’s bounds derive from its members, so the box still follows. Combo, node, multi-selection, nested-combo, empty-combo and dropEffect = "link" drags are unchanged. This is an upstream @antv/g6 5.1.1 bug (src/runtime/data.ts:203), so the override can go once G6 keeps the traversal on the combo hierarchy; drag-element-force needs no equivalent, as it moves per-node fixed layout positions and never takes the combo path.

  • Fixed click_select() reporting selected_node / selected_edge as null when the pointer had hovered the clicked element. The emit gate keyed off whether the element carried any state (getElementState().length) rather than its selection state, so with hover_activate() the hover’s active state made a plain click read as a deselect. It now gates on the behavior’s configured selection state, robust to coexisting states like active (#57).

  • combo_combined_layout() can configure the layout again. @antv/layout 2.0.0, bundled since the @antv/g6 5.1.1 bump, replaced the innerLayout / outerLayout pair with a single layout option (one configuration for every level, or a (comboId) => config callback where comboId is null for the outermost level) and renamed spacing to comboSpacing. The wrapper still sent the old names, which the engine ignored silently, so every call laid out with G6’s defaults (force between combos, concentric inside them) no matter what was passed. layout and comboSpacing are now forwarded; the removed innerLayout, outerLayout, spacing and treeKey raise an error pointing at the replacement instead of being dropped on the floor.

g6R 0.6.5

CRAN release: 2026-07-17

New features

  • Bumped the bundled AntV G6 engine from 5.0.49 to 5.1.1 (with @antv/g 6.x and @antv/g-svg 2.1.1, deduplicating g-lite so the SVG renderer works again instead of throwing Cannot read properties of undefined (reading 'fill') on a blank canvas). The bump improves create-edge assist-line tracking under zoom/pan (G6 now derives the cursor position via getCanvasByClient()), fixes a combo/layout element-type confusion that crashed the canvas renderer when a combo was added at runtime under g6R.layout_on_data_change (Cannot read properties of undefined (reading 'src')), and pulls in upstream APIs (hasNode()/hasEdge()/hasCombo(), nested self-loop edges, drag-element trigger config). The widget bundle is now a single self-contained g6.js (the split 185.js chunk was removed).

  • New placement = "label-bottom" for ports. When a node has a bottom label (e.g. labelPlacement = "bottom"), an output port with this placement snaps to the bottom-centre of the label background instead of the node body; it falls back to a normal bottom-of-node port when there is no label.

  • Default port radius increased from 4 to 6 so ports are easier to see and target.

  • Calmer port hover. Hovering a port no longer grows it ~2.5x into a rotating dashed + glyph (which read as flickering noise). Ports now keep their size and show a soft expanding ripple ring (in the port colour) only while the cursor is over them; the ripple stops when the cursor leaves. Disable it per port with ripple = FALSE. Ports also gain a background-coloured ring so they read as set into a small hole punched through the node / label surface (override with the haloFill port style), and the default fill adapts to the graph theme. The large invisible hit-area is kept, so ports remain easy to grab.

  • Port hover cursor is now a crosshair instead of a pointer, matching G6’s native create-edge affordance (“draw an edge from here”). The crosshair and the hover ripple only appear when the graph actually has the create_edge() behavior, so ports don’t advertise an interaction that isn’t wired up.

  • create_edge() now tolerates a small miss when grabbing a port. Previously an edge only started when pointer-down landed exactly on the (small) port glyph; a near-miss on the node body fell through to drag_element() and moved the node instead. Pointer-down now snaps to the nearest grabbable port within a tolerance proportional to the port radius, so a slight miss still starts an edge (#50).

Bug fixes

  • create_edge(): the hidden rubber-band assist node now carries a transparent src, so it no longer crashes the canvas renderer with Cannot read properties of undefined (reading 'src') when the consumer maps every node to an image node via a fixed node type. The node stays hidden, so the pixel is never shown. The SVG renderer is unaffected.

  • Fixed create_edge() overwriting a consumer-supplied drag_element() / drag_element_force() enable predicate. While drawing an edge from a port, create_edge() pauses node dragging and resumes it on drop. It previously resumed by hardcoding enable: true, which destroyed any custom enable function after the first edge creation (and toggled behaviors via an array, a no-op in current G6 where updateBehavior() matches a single key). The live enable of each drag behavior is now snapshotted (looked up by type, so a custom key still works) and restored verbatim on drop (#48).

  • Fixed the create_edge() rubber-band (assist) edge not appearing while dragging when the graph had no edge options configured. The assist edge style read graph.options.edge.style.zIndex, which threw and aborted assist-edge creation; it is now read defensively and falls back to G6’s default.

  • Fixed create_edge() drops being silently canceled below 100% browser zoom. The minimum drag-distance threshold was measured in canvas units, which scale with browser/graph zoom; below 100% a real drag shrank under the threshold and the drop was treated as a click. Drag intent is now measured in client (screen) pixels, which are zoom-stable (#52).

  • Fixed the old + port indicator reappearing on node selection. The legacy indicator was replaced by the hover ripple, but its shapes were still created and G6’s setVisibility() cascade (fired on selection/update) flipped them back to visible. The dead indicator shapes (add-inner, add-plus) and their animation helpers are now removed entirely, leaving only the ripple ring.

g6R 0.6.0

CRAN release: 2026-04-27

Breaking changes

  • Not a g6R change but bslib recently introduced the toolbar() function which unfortunately overlaps with the g6R one. From now, you’ll have to use g6R::toolbar() to avoid conflicts. In later releases, we’ll provide more prefixed functions like g6_toolbar.

New feature

  • Added better port support for nodes ports:
    • To enable it, you must pass a custom type to g6_node() such as custom-circle-node, custom-rect-node (We support 9 shapes, except HTML which does not handle port in the g6 library)
    • g6_node() get a new ports argument to define ports for each node. In the g6 JS library, ports are normally defined inside style but we consider they are too important to be hidden there. Now you can define ports directly in the node data, g6R automatically moves them to style.ports when rendering the graph.
    • New g6_port() function to create ports easily and wrap them inside g6_ports(). A port has a unique key, an arity that is the number of connections it can make or take and other style parameters inherited from g6. When giving a key to a port, don’t worry if key names collide between nodes, g6R automatically makes them unique by prefixing them with the node ID on the JS side.
    • 2 kind of ports have been designed:
    • When creating edges, if you provide sourcePort and/or targetPort within the style list, the edge will be connected to the corresponding ports. Validation is made so we don’t connect incompatible ports (e.g. connecting an output port to another output port) or connecting a port to itself.
    • create_edge() behavior was improved to work better with ports. For instance, you can’t drag from a port that is already at its arity limit. You can’t drag from a node if it has ports (drag from the ports instead).
    • Ports gain a label parameter to display text on the port.
    • In a Shiny context, showGuides allows to display connection guides when hovering over a port. Combined with input[["<GRAPH_ID>-selected_port"]] and input[["<graph_ID>-mouse_position"]], this allows to add and connect nodes on the fly at the guide location.
    • Use g6_update_ports() to update port (3 possible actions: remove/add/update) ports of existing nodes.
    • Use g6_get_ports() to get the ports of existing nodes. Specifically, you can call g6_get_input_ports() and g6_get_output_ports() to get only input or output ports respectively. This are only convenience functions.
  • Added new collapse parameter to nodes. This will only work if you use any of the custom-*-node node types (see below). Now if a node has children (vector of character node IDs), it can be collapsed or uncollapsed. collapse accepts a list of options via g6_collapse_options(). When a node has children set (character vector/list of node IDs expected), an option g6R.directed_graph is set to TRUE so that, when a connection is created between 2 nodes, we automatically establish parent/child relation and inversely when an edge or node is removed. You can also manually opt-in for this setup by setting options(g6R.directed_graph = TRUE). Importantly, the parent/child relations are only maintained if you use the g6R proxy functions. Using the direct JS G6 API yourself (like with graph.removeEdgeData(...) won’t do anything to keep the tree state in sync! The set_g6_max_collapse_depth() option controls which nodes display a collapse button based on their depth in the graph. Only nodes at depth <= maxCollapseDepth show collapse buttons. Defaults to Inf (all nodes with children are collapsible). Set to 0 to restrict collapsing to root nodes only. Set to -1 to disable collapsing entirely (collapse buttons are removed even when g6_collapse_options() is provided and nodes have children).
g6_node(
  id = 1,
  type = "custom-rect-node", # to enable use custom class
  children = c(2, 3),
  collapse = g6_collapse_options(collapsed = TRUE)
)
  • Added new collapse parameter to g6_combo(). Accepts g6_collapse_options() just like nodes. When collapse is provided and type is NULL, the combo type is automatically set to "rect-combo-with-extra-button". The combo collapse button now supports all the same configuration as nodes: configurable placement, r, fill, stroke, iconStroke, visibility (including "hover" mode), etc.
g6_combo(
  "combo1",
  collapse = g6_collapse_options(
    placement = "bottom",
    fill = "#f0f0f0",
    visibility = "hover"
  )
)
  • bubble_sets() and hull() now automatically set pointerEvents = "none" and zIndex = -1 by default. This fixes two issues when using the SVG renderer: overlay shapes no longer block pointer events (drag, click) on nodes, and they render behind nodes so they don’t visually cover collapse buttons or other node UI. These defaults can be overridden by passing explicit values.

  • Overlay plugins (bubble_sets(), hull()) now resize dynamically when nodes are collapsed or uncollapsed. Hidden members are temporarily removed from the overlay shape and restored when expanded again.

  • New rect-combo-with-extra-button combo type, the rectangular counterpart of circle-combo-with-extra-button.

  • Graph now uses ResizeObserver to detect container size changes (e.g. from DOM reparenting, panel resize, CSS visibility toggles) instead of relying solely on window resize events. The window resize listener is kept as a fallback.

Bug fixes

  • Fixed hull() label not displaying: the default labelMaxWidth was 0, which caused G6 to ellipsize the label to zero width. Changed default to NULL (consistent with bubble_sets()).

  • Fixed port + indicators incorrectly showing on at-capacity ports during node selection or drag. G6’s internal setVisibility() call during update was making all child shapes visible, and the cleanup was skipped when the cursor was hovering the node. Now the capacity-aware port logic is re-applied during updates while hovering.

  • Fixed ports with visibility = "hover" or "hidden" leaking as visible on the initial render. G6’s BaseShape constructor calls setVisibility() after render(), which recursively cascades the node’s visibility to every child shape (including port circles and indicators), overriding the per-port visibility mode. The correction now also runs on initial creation via a setVisibility() override, not just on subsequent updates. Additionally, the edge-created listener no longer force-calls showPorts on non-hovered nodes, so the + indicator no longer leaks onto nodes the user isn’t actually hovering after initial edges load (cynkra/blockr.dag#107).

  • Improvements to how drag_element() and drag_element_force() work with create_edge(). Now, the create_edge() can be drag and work with drag_element() as we handle the behavior conflicts/priorities JS side.

  • input[["<graph_ID>-state"]] now does not return unnamed lists for nodes, edges and combos. Instead, each sublist is named with the corresponding element IDs. This makes it easier to retrieve the state of a specific element when we know the ID.

  • Fixed tooltips() plugin being invisible inside a bslib / Bootstrap page. G6’s tooltip container is rendered with class="tooltip", which collides with Bootstrap’s own .tooltip { opacity: 0 } rule. The widget now ships a small CSS reset, scoped to .html-widget.g6 .tooltip, that restores opacity: 1 without affecting Bootstrap tooltips elsewhere on the page.

g6R 0.5.0

CRAN release: 2025-12-09

Potential breaking changes

Due to the new data validation for nodes, edges and combos, some existing code might break if the data provided to g6R functions is not valid. See the corresponding documentation section. If you had to pass custom data you can do it in the data slot.

New features and fixes

  • Support for svg rendering:
g6_options(renderer = JS("() => new SVGRenderer()"))
  • Fix issue which was preventing from removing a node from a combo.
  • Layout is not recomputed when calling data proxy functions, except if options("g6R.layout_on_data_change" = TRUE). In the later case, the layout is recomputed after drawing.
  • New option g6R.preserve_elements_position. If TRUE, and only if g6_options(animation = FALSE), the elements (nodes and combos) coordinates are preserved when updating the layout to avoid elements from jumping to new positions. Default is FALSE. A warning is raised if this option is TRUE and animation is TRUE to tell the user that the option will be ignored.
  • New g6_update_layout() proxy function to order the layout re-execution and optionally update its parameters.
  • New input[["<graph_ID>-mouse_position"]]: any click/right click or drag release event captures the mouse position. input[["<graph_ID>-mouse_position"]] contains the x and y coordinates of the mouse relative to the canvas. This is useful to add a node where the mouse was clicked, a context menu was triggered or the create edge was released on the canvas without a specific target.
  • Elements selected via brush_select() or lasso_select() have a custom input handler. This may give:
input[["<graph_ID>-selected_combo"]]
[1] "1" "2"
attr(,"eventType")
[1] "brush_select"

Notice the extra attribute, which allows to make a difference between click_select() and brush_select() events.

  • Get correct element type on click: it was possible that when clicking a combo, it appeared under input$<graph_ID>-selected_node instead of input$<graph_ID>-selected_combo.
  • create_edge() behavior improved: when creating an edge and it is release on the canvas, the edge isn’t cancelled and data are available. We added a targetType property which allows to know where the edge was dropped.
  • Added new elements API: g6_node(), g6_edge(), g6_combo() to create nodes, edges and combos respectively. We suggest to use them instead of passing lists or dataframes to g6 as they provide safety checks. We also added g6_nodes(), g6_edges() and g6_combos() which are internally used by some proxy functions like g6_add_nodes() to provide more flexibility.
  • New options("g6R.mode) that can be dev or prod (default). In dev mode, Shiny notifications are displayed in the UI whenever a JavaScript error happens (they are still available in the JS console).
  • g6_focus_elements(), g6_hide_elements() and g6_show_elements() gain more specific siblings: g6_focus_nodes(), g6_focus_edges(), g6_focus_combos(), g6_hide_nodes(), g6_hide_edges(), g6_hide_combos(), g6_show_nodes(), g6_show_edges(), g6_show_combos().
  • brush_select(), lasso_select() and create_edge() gain an outputId parameter which allows to manually pass the Shiny output ID of the graph instance. This is useful when the graph is initialised outside the shiny render function and the ID cannot be automatically inferred with shiny::getCurrentOutputInfo(). This allows to set input values from the callback function with the right namespace and graph ID. You must typically pass session$ns("graphid") to ensure this also works in modules.
  • Improvements to brush_select(): now it correctly returns the list of selected nodes, edges and combos, available via input$<graph_ID>-selected_node, input$<graph_ID>-selected_edge and input$<graph_ID>-selected_combo, thanks to the internal IDs refactoring. After a brush select operation, you can now shift click (depending on the click_select() multiple selection trigger settings you set, it might be another key) to add/remove elements to/from the current selection.
  • lasso_select() also gets the same quality of life improvements.
  • Fix: when selecting (simple select not multiselect) an edge/node/combo, if another type of element was selected, the corresponding input is reset. This avoids to accidentally delete a previously selected element when another type of element is selected.
  • Fix: state never get set on first render.
  • Fix #23: graph has to be re-rendered after dynamic plugin addition so that new elements like hull are drawn.
  • Fix #22: internal typo in JS function when an error was caught in the graph.
  • Add input$<graph_ID>-contextmenu to extract the type and id of element which was clicked in the context menu. This can be listened to from the Shiny server function.
  • Fix layout and behavior issues in some examples: drag_element_force only works when animation is TRUE. Also added autoFit = TRUE wherever required (manual layout vignette).

g6R 0.1.0

CRAN release: 2025-07-10

  • Initial CRAN submission.