Skip to content

Non-square icons are squashed instead of scaled to fit #2108

Description

@kmcginnes

A node icon that is not square is stretched into a square rather than scaled down to fit, so it renders distorted. Affects both the graph canvas and the DOM surfaces, consistently.

Reproduce

  1. Style a node type with a custom uploaded raster icon whose width and height differ — a wide logo shows it clearly.
  2. Look at the node on the graph canvas, and at the same node type in a search result row.

The icon fills a square box, distorted. Expected: scaled down to fit the icon box, keeping its aspect ratio.

Why it happens

Canvas. components/Graph/styles/defaultNodeStyle.ts sets:

backgroundFit: "none",
backgroundWidth: "60%",
backgroundHeight: "60%",

Per cytoscape 3.34.0 drawInscribedImage, background-width/-height override the image’s natural dimensions, and background-fit then scales whatever those left:

var w = imgW, h = imgH;
if (background-width  !== auto) w = pct * nodeTW;   // 0.6 * 24 = 14.4
if (background-height !== auto) h = pct * nodeTH;   // 0.6 * 24 = 14.4
if (fit === "contain") { var scale = Math.min(nodeTW / w, nodeTH / h); w *= scale; h *= scale; }

So both axes are forced to 14.4 and the image is stretched into a square.

background-fit: contain does not fix this — it runs after the override, when w and h are already equal, so it just scales the squashed square up to fill the node. Verified in headless Chrome against real cytoscape 3.34.0.

DOM. components/VertexIcon.tsx renders <img className="size-6 shrink-0">. Forcing both dimensions with the default object-fit: fill stretches the image the same way.

VertexSymbol is already correct — its <image> carries preserveAspectRatio="xMidYMid meet".

Fix sketch

There is no configuration-only fix: cytoscape cannot fit an image to a box and preserve its ratio at the same time, so the intrinsic ratio has to be known before the style is generated.

  1. Measure the raster’s natural size when it resolves (core/icons/iconRegistry.ts) and carry the aspect ratio on the resolved icon.
  2. Compute background-width/background-height from it, shrinking the shorter axis so the icon fits the 60% box instead of stretching to it — e.g. a 4:1 image becomes 60% / 15%.
  3. VertexIcon: add object-contain to the <img>.
  4. Generated SVG icons need nothing — they carry a viewBox and letterbox themselves.

Known costs, which is why this was split out rather than bundled into #2102:

  • Raster resolution becomes asynchronous, where today the url needs no work.
  • Adds an image-load failure mode, needing a fallback to square.
  • No test environment loads images, so measuring never settles without an Image test double in setupTests.ts — which changes the environment for every test file.
  • useBackgroundImageMap has to return dimensions alongside the image, so its return type and name both change, rippling into useGraphStyles.

Notes

Pre-existing behavior, unchanged by the icon pipeline rework in #2102defaultNodeStyle.ts has carried these values since before that branch. Distinct from #2105, which is about recoloring custom SVG icons rather than sizing them.


Important

Internal only — this issue is maintained by the core team and is not accepting external contributions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    customizationCustomization options for rendering graph data in non-default waysinternalSignals that the team will work on this issue internally.usabilityIssues relating to the ease of use of the UI or features

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions