Changelog
All notable changes to Cytoscape.js are recorded here. This file starts at the 4.0 line; for the 3.x history see the releases page.
The format follows Keep a Changelog, and this project follows semantic versioning.
[Unreleased] — 4.0.0
v4 is a rewrite of the model and the renderer. The public API keeps v3's
shape — cy.add(), eles.filter(), node.position(), the traversal and
algorithm surfaces, the alias spellings — while several v3 mechanisms were
removed rather than reimplemented.
Porting a v3 app: read MIGRATING.md. It carries the
recipe tables, the measured property-by-property diff, and the list of things
that compile and then behave differently.
Not released.
cytoscape@3remains the released library.
Added
The layout audit's instrument (round 125.10 / 125.11): the debug page's live spacing slider and airiness readout, an options panel with every option the selected layout takes and the
cy.layout()call it spells,debug/airiness.js's nearest-box probe shared with the suites, andnpm run benchmark:layout-audit— one layout on one of the page's networks, headless, with the page's own sheet, printing the columns a sitting reads.condenseoncircleandradial(round 125.3 / 125.5, grid's spelling): size the ring(s) by the nodes andavoidOverlapPaddingrather than by the viewport box, so the gap between neighbours is the option and the box only centres. Off by default; the rings fill the box as before.A
seedon therandomlayout (round 125.8): the same seed places the same graph the same way, run after run — the stability every other built-in has. Omitted, the scatter is unseeded as before.Taxi tracks — automatic turn distances per bundle (round 124, item 59):
taxi-turn: autotakes an edge's px turn from a track the store assigns from live positions, so the runs of different fan-outs sit on distinct lines in the gap instead of one;taxi-track: source | target | family(defaultsource) says which edges share a track andtaxi-track-spacing(default 10) how far apart tracks sit. Style-side: edges keep routing themselves after a drag, under every layout. Flow gainsedgeSep(default 10): the gap below a rank grows tomax(rankSep, tracks × edgeSep + 20), and the long edges into one target share one chain anchored at the target so the taxi leg meets no node body on the way down. The track sweep runs whole once per geometry epoch (8 ms on a 1.9k-edge DAG, 160 ms on a pathological 8.7k-edge one); a conflict component past 128 bundles keeps the staircase order rather than the crossing rule (124.7).Component packing on the discrete layouts, and the
packlayout (round 123, item 58):packComponents: trueoncircle,concentric,grid,breadthfirstandradiallays each disconnected component out on its own — one ring, one set of rings, one grid, one tree per component — and shelf-packs the drawings largest first undercomponentSpacing,componentGroup,componentOrderandgroupSpacing, force's spellings; off (the default) every layout is v3's one figure. Locked nodes are left out and the packed field is moved off them; an explicitboundingBoxholds the field.cy.layout({ name: 'pack' })re-packs the components where they stand under the same options, the largest held — layout-utilities'separateComponentswith grouping and order, translation only. Breadthfirst draws its trees as blocks by default: each rank ordered tree-first, each tree a column band with its root over the middle of its subtree, the singletons a block of rows, the bands wrapped into shelves; one component is v3's picture exactly.Force's re-pack takes a grouping and an order (round 121,
componentGroup,componentOrder,groupSpacing): a function of each disconnected component's description ({ nodes, size, width, height }) keys the groups, which pack on their own and stand in a row by key; a comparator orders the components ahead of the largest-first order — so the EnrichmentMap shape (negatives left, positives right, rows by size with each row by score) is one force call. And components of five nodes and up turn to a canonical angle at the settle — the principal axis flat, or a ring's farthest node up — undertidyComponents.Force's smallest components take canonical shapes (round 120,
tidyComponents, default true): a pair stands as a vertical barbell, three make a point-up triangle, four a diamond, sized to the edge length and the bodies' clearance — so the re-pack lays small components out in rows by size, and two centre-aligned labels on a pair never sit side by side. A component with a locked node, or with mixed per-edge lengths, keeps the sim's shape.The infinite force run (round 118.3,
infinite: true): the live force-directed layout that never ends, ticking only while the field moves — at rest it costs nothing — and reheating on a drag (the grabbed node pinned for the gesture, its neighbourhood reflowing), aposition()write, an added or removed element, orlayout.reheat();stop()lands the positions as they stand. On both executors. v3 had this only as the cola extension'sinfinite.Force's overlap separation reaches 25k (round 118.1, open call 57). The settle's separation handed the 25k random scene back with more overlap than the sim gave it; a component of a thousand nodes or more with most of its nodes touching is now expanded about its centroid by what its median overlapping pair asks for before the local passes run, and a best-state guard restores the shallowest field any stage reached. 5k, 10k and 25k random scenes come out with no overlapping bodies, the pass runs in under 3 s at 25k, and the settled field grows under 2x.
Force can keep bodies apart in the sim, and honours
boundingBox(rounds 116–118).avoidOverlaponforcenow says how:true/'settle'(the default) separates the padded node boxes exactly after the settle;'sim'runs a separation sweep — the settle's own primitive — after every tick on the CPU and GPU executors alike, so a streamed run is held open as it streams and ends clear with no settle pass;'both'does both;falseneither. The per-tick sweep is opt-in (117, open call 56): the settle's pass is cheaper for a one-shot run. (116.1 built the in-sim half as a short-range contact force and 117 made it opt-in asavoidOverlapInSim; 118.2 replaced the force with the projection — a force bounded by its gap clamp lost to a dense graph's spring pressure and annealed every run to the floor — and the flag with the mode.)boundingBoxonforcenow holds the drawing — scaled down, never up, so every body lies inside, then centred (flow's rule, shared asfitBodiesToBox); a pinned node or constraints hold it back, as they hold the component re-pack. A locked child stays where it is when its compound parent is positioned, shifted or dragged — its own subtree with it — and the parent re-derives about the stayers and the movers (v3's rule).Layout cleanup (round 114): every layout reads one set of node dimensions, labels included by default (
nodeDimensionsIncludeLabelsnow defaults totrue); locked nodes hold their place against position writes, position tweens and every layout, and are obstacles for overlap avoidance;forcegainsanimateLive(the streaming run),avoidOverlapandavoidOverlapPadding, and itsanimate: truetweens to the settle like every other layout, with the shared finisher options (spacingFactor,transform,animateFilter, duration, easing,zoom,pan) applying; the extension contract gainsctx.nodeDimensions(),ctx.components(),ctx.finish(), overrides onctx.layoutPositions()and body-boxpackComponents; flow's animated runs fit again, and a lonezoomorpananimates. Radial gainsavoidOverlap(rings grow to clear it); concentric's chord rule reads a box's diagonal so two squares cannot meet corner-on; flow'sboundingBoxholds the bodies rather than their centres.test/layout-quality.mjsasserts placement, fit, overlap, locks, animation and component separation for every layout.The flow layout (round 112): a built-in Sugiyama-class layered layout —
cy.layout({ name: 'flow' })— with greedy-FAS cycle removal, GKNV network-simplex layering, weighted crossing minimization and Brandes–Köpf coordinates, replacing the dagre extension for most uses. Emits node positions only: pair it withcurve-style: taxiand edges route themselves, staying correct when nodes are dragged. Options includedirection,nodeSep/rankSep,layering(network-simplex|longest-path|auto),thoroughness, per-edgeminLength/edgeWeight(numbers, score mappings or functions) andrankConstraints(min/max/sameid lists). Measured against dagre and elkjs by the layout-quality harness (npm run benchmark:layout-quality).Faithful positionless round-trips (round 112):
cytoscape.toColumnarElementsnow omits the positions column when no input definition carries a position (previously it fabricated an all-zero column), so a positionless graph serialized through the wire comes back positionless — consumers that lay out at load can tell the difference again.Force-layout constraints (round 85.2, absorbing fcose's main draw):
alignment: { horizontal, vertical }id-array groups (a locked member pins its group) andrelativePlacement: [{ left, right, gap? } | { top, bottom, gap? }], with fixed nodes spelledlock(). Projection after each integration step; unknown ids, placement cycles, and contradictory locked members throw at start. Constrained runs take the CPU executor (measured: ~26 s vs ~0.4 s silent GPU at 25k — accepted for v1, since the constraint population runs at fcose sizes where the CPU settle is seconds).A columnar, CPU-canonical model. Elements live in typed-array columns with stable slots, per-column coalesced dirty spans, a CSR adjacency index and a dictionary-encoded
data()sidecar. Reads stay synchronous.A WebGPU renderer: SDF node shapes, curved-edge families evaluated in the vertex stage from live positions, compute culling with indirect draws, GPU picking with a synchronous CPU fast path for nodes, an SDF glyph atlas, early-z, and an adaptive render scale. Gestures hit-test with v3's halos (8/24 rendered px around edges for mouse/touch, 2/8 around nodes), arrowheads are hit targets (hollow counts as filled, as in v3), and pressing any element — edges included — shows v3's
:activeoverlay;cy.pick( x, y )stays exact.Pointer cursors (round 89): the canvas carries the standard affordances —
grabover a draggable node,grabbingwhile it or the background is being dragged,pointerover any other interactive element,crosshairwhile box-selecting — and mirrors an active drag onto the document element, so the affordance survives a pointer-captured drag that leaves the canvas. v3 set no cursors at all, so idle over background stays''(inherit) and the userlandmouseover/mouseoutrecipe every v3 app used still shows through.pointerCursors: falseturns the writer off entirely; an object overrides individual entries ({ pan: 'move' }). A touch pointer never gets a cursor.cy.$()is back (round 64) as a plain alias ofcy.filter()over the query/predicate forms (selector strings still throw), andcy.byId()joins$id/getElementByIdas the brevity id lookup.cy.collection()now throws if passed any argument — it used to silently ignore one where v3 builds from it.Per-element bypasses (round 63): the stylesheet's
bypassessection —{ bypasses: { id: { prop: constant } } }— with v3's method spellings as sugar (ele.style( name, value ), the object form,removeStyle( name? ),removeCss). A bypass beats every sheet rule (default-sheet selection included), survives remove/ re-add of its element, and exports fromcy.json()— better than v3, which drops bypasses on export. Constants only; a fullcy.style( sheet )replaces the section (spread the exported sheet to keep it). Style prop keys accept dash-case and camelCase everywhere (foo-bar≡fooBar), the bypasses included.Structured queries and predicates replacing the selector language —
cy.nodes( { selected: true } ),cy.nodes( { data: { w: { gt: 1 } } } ), and plain functions for everything richer.Element state is a style condition, which is what replaces v3's state selectors:
{ when: { selected: true } },{ active: true },{ locked: true },{ grabbed: true }and the rest, on any property. Each takes a boolean, so v3's negative selectors are the same key withfalse, and the same keys work as query keys. v4's default stylesheet carries v3's:selected,:parent:selectedand:activeblocks, spread before your own — so declaring the property replaces the rule, exactly as in v3.A serializable mapper DSL for style:
linear/log/sqrt/pow/symlog/diverging/ordinal/threshold/quantizescales, OKLab colour interpolation with named schemes, andcaseconditionals. Paint channels evaluate in a compute kernel; anything read by culling, picking or a columnar scan stays CPU-canonical.Columnar and binary loading.
cytoscape.toColumnarElements(),cytoscape.serializeElements()/deserializeElements(), both accepted directly byoptions.elementsandcy.add(). Numeric columns deserialize as zero-copy views.Style transitions (
transition-property/-duration/-delay/-timing-function) and animation controls (pause/resume/reverse, read-onlyprogress/paused).The
forcelayout — GPU-native force-directed layout, animating live at 100k nodes, with a CPU reference executor for headless and compound graphs; spectral (landmark-MDS) initial placement, degree-normalised springs, grid-pyramid long-range repulsion, per-compound gravity and nesting (gravityCompound,nestingFactor), and component packing (componentSpacing).The
radialtree layout (round 85.1, #2493) — hierarchy-aware: each subtree takes a contiguous angular wedge sized by its weight, multiple roots partition the circle in caller order, and orphan components get wedges of their own. Measured 2.8× faster than the breadthfirst-circle recipe at 2,000 nodes.Data-driven layout mappings (round 85.3, #1514):
{ data, scale?, range?, invert?, default? }onforce.edgeLengthandconcentric.concentric,{ data, order? }ongrid.sort,circle.sortandbreadthfirst.depthSort. Function forms stay as escape hatches; the objects are the canonical, serializable spellings, resolved once at layout start, with loud validation (unknown keys, wrong-kind columns,scale/invertwithout arangeall throw naming the option).A registry-free extension contract:
cy.layout( { impl } )runs an imported class or object;LayoutContextis columnar-first, and it,LayoutImplandCustomLayoutare exported types, so an external layout author writes against real types rather thanany.ctx.packComponents( spacing? )(round 87.1) packs laid-out disconnected components into non-overlapping shelves — v3 layout-utilities'separateComponentsin one call, and the same packing theforcelayout'scomponentSpacinguses.Border and outline stroke styles on every shape —
border-style(solid/dashed/dotted/double, with v3's erase behaviour fordouble),outline-style, andborder-dash-pattern/-offset. Dash patterns follow each shape's outline with the phase anchored where v3's canvas path starts, including exact elliptic arc length.chart— v3's 101 numbered pie/stripe properties as one list-valued family with data-driven values, scheme palettes and donut holes.visibilityas a paint-only style property beside the structuralshow()/hide().Per-side compound padding (round 85.4):
padding-left/-right/-top/-bottomin theparentssheet group, each a number of px or'N%'resolved perpadding-relative-to; an unset side takes the uniformpadding.Slot compaction —
cy.compact()(aliascy.gc()) plus an automatic trigger, shrinking scan widths and buffers to the current graph rather than its peak.boxSelectionMode('contain'|'overlap'),boxSelectionIncludesLabels,wheelSensitivity,desktopTapThreshold,touchTapThreshold,tapholdDuration.eles.labelBoundingBox(), and labels joinboundingBox()/fit()by default.TypeScript declarations built from the source JSDoc, so the API documentation is hover text in an editor.
GPU executors for the expensive whole-graph algorithms (round 65).
markovClustering,affinityPropagation,pageRank,floydWarshall,betweennessCentrality,kMeans,kMedoids,fuzzyCMeansandhierarchicalClusteringacceptexecutor: 'cpu' | 'gpu' | 'auto'(default'auto'): the CPU is the bit-reproducible reference, the GPU runs WGSL compute kernels where WebGPU exists, and'auto'picks per measured per-family crossovers. Measured on an RX 570-class adapter: Markov clustering up to 663× (31.3 s → 47 ms at 1,024 nodes), k-medoids up to 146×, fuzzy c-means up to 70×, Floyd–Warshall up to 28×, betweenness up to 18×, k-means up to 25×. PageRank and hierarchical clustering route to the CPU under'auto': the CPU PageRank iterates sparsely (O(E) per iteration — orders of magnitude on sparse graphs) and the hierarchical merge engine went flat-typed, leaving the GPU no edge to win there.Eight new algorithm families, designed matmul-first for the GPU tier (rounds 69–70), all on the same async
executorcontract and with no v3 counterpart. Round 69:eles.triangleCount()(per-node triangle counts, local clustering coefficients, total triangles and transitivity — A²∘A on the GPU),eles.neighborhoodSimilarity()(pairwise Jaccard / cosine / overlap coefficients over neighbor sets — A·Aᵀ on the GPU) andeles.katzCentrality()(attenuated walk counting; like PageRank its sparse CPU iteration owns'auto'and the GPU path serves an explicit'gpu'). Round 70, aimed at network-biology workloads:eles.randomWalkWithRestart()(seed-set network propagation — the disease-gene-prioritization primitive) andeles.randomWalkWithRestartProximity()(the all-pairs proximity matrix, a Neumann matmul iteration on the GPU),eles.heatDiffusion()/eles.heatKernel()(HotNet-style heat propagation; exp(−t·L) by scaling-and-squaring on the GPU),eles.effectiveResistance()(resistance distance and commute time off the Laplacian pseudo-inverse — f64 elimination on the CPU, Newton–Schulz matmul iteration on the GPU; O(n³) on both sides, so the GPU wins at every density),eles.simRank()(the Jeh–Widom recursive similarity, two matmuls per iteration) andeles.motifCensus()(the sixteen-class Holland–Leinhardt triad census — '030T' is the feed-forward loop — computed from seven trace primitives and pinned by a brute-force classifier spec). The whole-collectionclosenessCentralityNormalizedjoined the async tier in round 69: its GPU path rides the blocked Floyd–Warshall kernels and folds each distance row on the device, reading back n floats instead of the n² matrix.
Changed
Label boxes are measured where a canvas exists (round 125.1): the store measures label blocks with
measureTextat style time in a document or with anOffscreenCanvas, so a layout run before the first frame — thelayout:option, an app's load — separates the boxes the frame will draw. Before, an estimate stood in until the first frame and 154 of em-web's 569 labels lay wider than it, so label-inclusiveavoidOverlapleft 14 overlapping pairs at load. Headless keeps the estimate.boundingBox()andfit()before the first frame see the same boxes.Force's small-component shapes survive the settle's separation pass (125.1): the tidy pair, triangle and diamond are sized by the exact per-pair separation and the separation sweeps stay within a component when a re-pack follows, so
avoidOverlapno longer pulls a diamond apart.Flow reads extents per side and per direction (125.2): a label hung to one side of its body no longer counts on both sides, a rightward flow separates its ranks by heights and its rows by widths rather than the reverse, and a node nothing aligned (a free singleton) moves to the median of its neighbours within its rank's slack instead of the leftmost feasible x — the "IRAK1 far from its only parent" case, 1,632 px to 68. Reactome with labels: 21.7 to 17.0 Mpx²; the Greek gods rightward with labels: 3.42 to 1.62.
Radial infers a component's true roots (125.3, the maintainer's sitting): the nodes with no incoming edge, falling back to the maximum-degree rule only for a component that has none (a cycle). Reactome's centre was Innate Immune System (degree 17) with the one true root on ring 1; the hierarchy now reads outward from its root.
Breadthfirst is sized by the pixel viewport (125.4), as grid, circle, concentric and radial are; it read
cy.extent()in model coordinates, so its drawing scaled with the zoom at the moment it ran (nine times airier on a page fitted at zoom 0.11). At zoom 1 nothing changes. A compound parent is never a root (an edgeless scope parent made every npm-deps positionNaNunder the undirected default).The shelf packer stacks short components under earlier columns (125.9): a row holding one tall component and several short ones used to leave the short columns empty below; now a box goes under the leftmost earlier column of its row with the room (first fit, spacing kept, like-sized boxes never stacked, off under a
componentOrder). em-web under radial +packComponents: packing efficiency 0.66 to 0.83; the pack layout 0.58 to 0.67.Preset rejects a half position (125.8): a map entry of
{ x: 100 }wrotey: NaNinto the store and anullaxis became 0; both now throw aTypeErrornaming the node.The line casing draws per edge, in v3's order (round 124.4):
line-outline-*used to be a global pass under all lines — a halo against nodes only; now each edge draws its casing then its line in z order, so a later edge's casing gaps an earlier edge's line where they cross, as v3 draws it (the parity scene's difference fell from 72 px to 10). Anyone using a casing sees gapped crossings now; the old global-halo look is the edge underlay.The non-presenting GPU force batch is priced before doubling (round 121.5): the device's time on the last completed batch bounds the next to what a 100 ms budget buys, so the ramp no longer overshoots on a heavy scene (the 25k scene's batch rides 3–16, and no run frame passes 50 ms). And the run's synchronous start is shorter (121.6): the spectral seed's BFS is typed, 190–204 → 70–75 ms at 25k × 50k, bit-identical.
The GPU force executor's iteration cost (round 119): the grid's cell scan is a 256-thread workgroup scan rather than one thread walking every cell (3.2 ms an iteration on 569 nodes before, 19 ms at 10k), and a run nobody watches mid-run —
animate: false, oranimate: true's tween — batches up to 64 iterations per frame by what the device keeps up with instead of the live stream's three. em-web's silent run 1.5 s → 0.3 s; the 25k scene 11.8 s → 3.3 s.stepsPerFramenow paces onlyanimateLiveandinfinite.Force's
thresholddefaults to 2% of the mean ideal edge length (round 119.3; 1.2 px at the default length) foranimate: falseand the tween, measured to reproduce the 0.1 px settle's edge lengths, stress and overlaps within noise on seven fixtures at 2–3× the speed;animateLiveandinfinitekeep 0.1 px, since a stream's stop is visible. Passthresholdto pin either. em-web's silent run on the page: 0.3 → 0.15–0.24 s.The stylesheet is
{ nodes, edges, parents, core }— an object of property objects, not a list of selector blocks. State-dependent styling is acasecondition rather than a:selected-style block.Draw order is structural and stays that way: compound parents, then edges, then leaf nodes, then labels; slot order within a stream.
Picking answers in the reverse of that order: leaf, then edge, then compound parent (round 97), so what you see is what you pick. A click or hover on an edge crossing a parent's body now answers the edge, where it used to answer the parent the renderer had drawn underneath it; nesting depth does not change the order, and among parents the deepest still wins.
cy.pick()states the order as contract. Two visible consequences: hover styling on a parent body stops firing where an edge lies under the cursor (v3 behaves this way, so it is parity restored), and a click inside a parent body waits for the edge tile — one GPU roundtrip — where it used to answer synchronously. v3 ordered these byz-index/z-compound-depth, which v4 does not have, so a deeply nested v3 parent could beat a shallower edge where v4's edge wins. Dragging is unchanged: a press still grabs the parent body it lands on.forceunderanimate: falseon a rendered flat graph is async (round 87.2): executor choice is availability-driven, so a flat rendered graph with a device hands integration to the GPU for both animate values, where it used to run the CPU sim synchronously on the main thread. Presentation does not pick the executor: since round 114animateLive: truestreams positions to the screen per frame,animate: trueintegrates silently and then tweens to the settle, andanimate: falseintegrates silently off-mirror while the screen holds the pre-run frame until the settle lands in one write. The visible consequence: positions are readable atlayoutstop/promise(), not on the line afterrun(). Headless and compound-graph runs keep the synchronous CPU path, so specs and servers are unaffected.Animations run concurrently by channel and sequence by promise; overlapping channels evict the older animation in place.
The expensive whole-graph algorithms are async (round 65; the closeness family joined in round 69): the executor-tier methods above return promises —
awaitthe call, then use the result exactly as in v3. Runtime option validation on them surfaces as a rejection; a badexecutorvalue still throws synchronously. The traversal tier (bfs,dfs,dijkstra,aStar,bellmanFord,kruskal, components, degree centrality and the single-rootclosenessCentrality) stays synchronous; only the O(n³) whole-collectionclosenessCentralityNormalizedmoved.hierarchicalClustering'smeanlinkage works (round 65.10) — a deliberate deviation: v3's mean linkage never assigned the cluster sizes its weighted-average formula read, so the first mean merge produced NaN distances and the linkage silently degenerated. v4 tracks sizes;meanis the weighted-average linkage the docs always claimed.Colours tween in OKLab, matching the mapper default (v3 tweened per-channel in sRGB).
spring( bounce )replacesspring( tension, friction ).Default
curve-styleisstraight(v3:bezier), and defaulttext-valignisbottom(v3:top).cy.elements()returns nodes then edges, not mixed insertion order.Positions are Float32 (~7 significant digits).
Compound event bubbling is v3's, with the remaining ordering deviation confined to within a phase (registration order).
stop( jumpToEnd )— theclearQueueargument is gone with the queue.font-family,font-style,font-weightare global constants, one face per glyph atlas.Rendering requires WebGPU; headless requires nothing.
Comparing elements across two instances throws instead of answering wrongly. Element identity is a slot in one store, so the first node of one graph and the first node of another used to compare as the same element:
same()returned true,intersection()returned everything,difference()returned nothing, andunion()silently dropped the other graph's elements. The twelve affected methods (same,anySame,contains,indexOf,union,difference,intersection,symmetricDifference,diff,allAreNeighbors,edgesWith,edgesTo) now reject a collection from another instance.A corrupt binary payload fails fast rather than allocating for what it declares. Three cases found by fuzzing — an out-of-range dictionary index, an over-long packed-id blob length, and an impossible data-key count — could hang a load or take tens of seconds before erroring; each now throws a contract error naming the field.
Removed
- The round-90 API review's parity baggage:
cy.forceRender(),cy.batchData(),cy.mutableElements()(it waselements()by another name),onRender/offRender(usecy.on( 'render', … )), and thebind/unbind/listen/unlistenlistener aliases — the event surface follows Node'sEventEmitterspellings, pluspon. Demoted rather than removed (working, but@internaland out of the typed surface):cy.renderer()—cy.stats()is the public frame-stats snapshot in its place —instanceString(), thesilentPosition(s)/silentShiftwrites, theStyleEngineand animation machinery, and theViewportclass. - Selector strings, everywhere — and
cy.$()with them (cy.$id()is the id lookup). Passing one throws, naming the replacement. - Classes (
addClass/removeClass/toggleClass/hasClass/flashClass) —data()plus mappers is the replacement. - Style functions (
( ele ) => props) throw; per-element bypasses are back since round 63 (see Added), spelled as in v3 and canonically the stylesheet'sbypassessection. - CSS-string stylesheets and
cytoscape.stylesheet(). z-index,z-compound-depth,z-index-compare,sortByZIndex,zDepth.restore(),clone(),copy()and the import form ofcy.json(): removed elements are terminally dead.- The animation queue, the
queueoption and thestepcallback (all three spellings throw). - Custom easing functions —
cubic-bezier()andlinear()cover any drawable curve. - Event namespaces: a type is matched whole, so
'tap.ns'is one literal name. - The
vmouse*aliases and raw mouse/touch re-emits (mousedown,click,touchstart, …) —pointer*is their modern spelling.mouseover/mouseoutstill fire. These names still register and then never fire, because custom event names must stay legal. cy.notify()/noNotifications()— the renderer is dirty-driven.renderTo; per-elementfont-family; viewport-fixed labels.- The canvas-era performance options —
hideEdgesOnViewport,textureOnViewport(+outside-texture-bg-*),motionBlur,motionBlurOpacity. - Style properties:
background-blacken,bounds-expansion,content,position,display,text-metrics,box-selection,box-select-labels,edge-text-rotation, themin-*-bias-*quartet, the singularcontrol-point-distance/segment-distance/segment-weight/segment-radiusspellings, themid-*-arrow-fill/-widthpairs, and the numberedpie-N-*/stripe-N-*families. The no-dash shape spellings (roundrectangle,cutrectangle,concavehexagon) throw in all three enums that took them. - The
coselayout — not ported;forceis v4's answer. - The extension registry — no
cytoscape.use(); extensions are imports.
Not yet implemented
text-border-style(the label box border does not dash).border-style/outline-styleandborder-dash-pattern/-offsetthemselves work on every shape;border-cap/border-joinare dropped (dash ends are perpendicular cuts by construction).- Core, collection and renderer extension points.
Decided against rather than pending: cytoscape.warnings() (errors throw
and warnings warn, with no toggle over either — the fail-loudly contract
stands whole) and functional preventDefault() for v4's own gesture
defaults (the explicit toggles are the gesture-control surface; the call
reaches the browser's default only).
Known deviations
Accepted differences from v3's rendering and semantics — arrow tips on
approximate boundaries for some shapes, butt caps on layer strokes, outline
dash phase on polygon-family shapes, a conservative edge-label bounding term, no
decimation on the curved edge stream, and others — are enumerated in
src/README.md under "Known deviations from v3". Each is recorded where the
feature is described, with the reason.
One is worth naming here because it is visible in ordinary styling: v3 makes
a hollow or translucent arrowhead read as one shape with its edge by erasing
the head's footprint from the canvas, and v4 shortens the line instead — no
extra pass, and the same pixels wherever the head covers the line. It does
not reach mid arrows, which sit mid-line, so arrow-fill: hollow on a
mid-source/mid-target head still shows the line through it.