Repository navigation
Pivotick 3.0.0
A large graph is mostly repetition, and Simplify folds it away: nodes that play the same role collapse into one group drawn in their place, without touching the data. The interface now speaks the host's language, with complete Chinese, French, German and Portuguese catalogues bundled, and so does the documentation site.
Nodes pick their drawing from the zoom, edges into clusters are drawn at any depth, and a structured layout gives a small graph the same picture every time.
Read Breaking changes before upgrading: a few defaults change what an existing graph looks like, and each has a one-line opt-out.
Simplify: fold what plays the same role
UI.simplify.rules lists the rules, in the order they run, and a new Simplify rail mode switches each one and says what it folded.
- Built-in rules fold nodes of one type linked to exactly the same nodes (a hub's leaves are the simplest case), chains that hang off one node, nodes with too few links or outside the k-core, and whole neighbourhoods found with the Leiden algorithm.
- A rule of your own is a
partition(view)returning a key per node. Nodes sharing a key become one group. - A group is drawn as a node, a disc in its type's colour with the count on it, and acts for its members: open it, pull a member out, select, pivot or delete them all. Search finds folded nodes and lights the groups holding them.
- Group a selection by hand from the bulk bar or the node menu. Group, rename and ungroup are undo steps.
- Pivot results can land grouped, one group per type, with
pivotIngestGroupedor Review's Ingest in a group. - Groups are view state.
getNodes(), the history, the table and exports see the real nodes.
The UI in the host's language
GraphOptions.i18ncovers every string the library writes: the rail, menus, sidebar, table, pivot panel, history, toasts, tooltips andaria-labels.localepicks a bundled catalogue (zh,fr,de,pt),messagesreplaces text by key, andtranslatebridges an existing translation library. English fills every gap.localealso formats counts, lists and sorting, and plurals are right in English too: no more "1 nodes".- Gettext PO catalogues ship in
locales/, checked at build time for completeness, plural forms and placeholders. - The documentation site is in English, Chinese, French, German and Portuguese, guides and API reference alike.
Detail that follows the zoom
NodeStyle.tiersgives a node several drawings, a dot on an overview, a chip once there is room, a card up close, chosen by the size it renders at. The layout footprint stays put, so nothing moves when one drawing gives way to the next, and the two cross-fade.Nodetyle.focusTieris the drawing for the node under the pointer, at a readable size whatever the zoom.- Labels too small to read are not drawn (
render.minLabelFontSize, 9px). On a 2500-node graph that cuts a tick's reposition pass from 23.5ms to 7.0ms. textMaxWidthandtextFontSizecap a node label's width and set its font size.
Edges across clusters, at any depth
One rule decides what the canvas draws for an edge touching a cluster: each end lands on the node on screen for it, and edges landing on the same two nodes share one line. Edges from inside a cluster to outside it, and edges two or more levels down, are now drawn. A line standing for one edge looks like it and clicks through to it, and filters reach a closed cluster's children.
Pivots
- Fetch & ingest lands a pivot's results without triage.
menuChoicesturns a pivot's row in the Pivot ▸ submenu into one row per choice.- A facet can declare a
default, and a summary can leave its total unknown. - Saving got finer:
graph.pivots.save({ elements })writes only what you name, a save can open a prompt and be cancelled, andpivotSaveControls: falsehides the Save buttons. - Rim badges show only while Pivot mode is open.
Everything else
New options
layout: { type: 'structured' }: a deterministic layout for small graphs, with fans of leaves lined up beside their hub and labels on the free side.FilterFieldConfig.negateand an is / is not switch on every filter field.UI.topBar.actions: host pills in the top bar, with split-button menus.UI.emptyState: a card on a canvas with no node and no note.UI.busyIndicator: a spinner while an async hook is thinking.UI.tooltip.enabledtakes a per-kind map or a function.UI.neighborsPanel.graphoverrides the neighbour graph's render and layout.render.minFitScaleandrender.fitAnchorfloor the opening fit, andreadywaits for it.graph.addNote(options),note.toJSON()andgraph.setNotes();Noteis exported.- A
propertiesPanel.renderormainHeader.renderreturningundefineddraws the default.
Interaction
- A right-click on a multi-selection acts on the whole selection, and table rows get their element's context menu.
- Del hides the selected nodes, as one undo step.
- The context menus read in one order: Pivot, group entries, the rest, delete last. The canvas menu gains Release pinned nodes.
Fixed
- A second graph on the same page draws its own arrowheads.
graph.updateDatare-renders once per call instead of once per element.simulation.warmupTicks: 0keeps given positions, andsimulation.layoutis honoured.- A graph built in a hidden or resizing container fits once it has a size.
- Selecting a node inside a closed cluster lights and centres the cluster.
- A
viewercanvas takes the height its container gives it. graph.removeBySourceis recorded in the history and can be undone.- Portaled tooltips and menus follow
UI.theme. - An unlabelled edge no longer carries a label container, which cost almost all of a tick's reposition pass on a 1000-node graph.
Breaking changes
Most apps need no change. The defaults are the ones to read.
| Before | After |
|---|---|
| Labels drawn at any zoom | Hidden below 9 rendered px; render: { minLabelFontSize: 0 } restores them |
| A blank empty canvas | A card; UI: { emptyState: false } removes it |
| Rim badges outside Pivot mode | pivotRimBadgeVisible: 'always' |
| App menu entries below Delete | Now above it |
A viewer relying on a 300px minimum height |
Set a height on its container |
warmupTicks: 0 running the full warmup |
Runs none; leave it unset for the default |
Markup in Flyout.headerRow / sectionLabel / toggleRow |
Plain text |
edge.isCrossCluster, get/setSubgraphFromNode, get/setSubgraphToNode, writable isSynthetic |
Gone; stand-ins are drawing only and getEdges() returns real edges |
'endpoint' in the Edges tab for an edge into a closed cluster |
'nested' |
A switch over HistoryKind |
Add 'group' and 'removal' |
A content hook returning null printing "null" |
Renders nothing |
The full list is in CHANGELOG.md.