cytoscape-canvas-underlay 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,7 +8,7 @@ A [Cytoscape.js](https://js.cytoscape.org) plugin for rendering image/PDF backgr
8
8
  - **Pan clamping** — hard boundary or iOS-style rubber-band with spring-back
9
9
  - **Minimap** — DOM-based crisp image rendering, two viewport styles (`dim` / `rect`), auto-hide, full customization
10
10
  - **Adaptive PDF quality** — low-quality render during interaction, high-quality on idle
11
- - **Rich navigation** — `fit`, `fitAll`, `panToElement`, `panToRegion`, coordinate conversion
11
+ - **Rich navigation** — `fit`, `fitAll`, `panToRegion`, coordinate conversion
12
12
  - **Layer visibility** — independently show/hide drawing background and graph layer
13
13
  - **CSS filters** — invert, brightness, contrast, saturate, grayscale (invert applied first so brightness/contrast work correctly on inverted images)
14
14
  - **Rotation** — rotate main or additional drawings (0, 90, 180, 270 degrees)
@@ -147,13 +147,14 @@ api.off('zoom'); // remove all zoom handlers
147
147
  ### Navigation
148
148
 
149
149
  ```js
150
- api.fit(); // fit main drawing
150
+ api.fit(); // fit main drawing (instant)
151
+ api.fit({ animate: true }); // fit with animation (300ms)
152
+ api.fit({ padding: 50 }); // fit with 50px padding
153
+ api.fit({ animate: true, duration: 500, easing: 'ease-out' });
151
154
  api.fitToDrawing('trace-1'); // fit specific drawing
152
155
  api.fitAll(); // fit all drawings
153
156
  api.panTo(500, 300); // center on world coordinate
154
157
  api.panTo(500, 300, 2.0); // center + set zoom
155
- api.panToElement('node-id'); // center on cytoscape element
156
- api.panToElement(cy.$('#node-id')); // also accepts element reference
157
158
  api.panToRegion(100, 100, 400, 300); // fit a world-coordinate region
158
159
  ```
159
160
 
@@ -255,7 +256,7 @@ const inside = api.isPointInDrawing(world.x, world.y);
255
256
  | Option | Type | Default | Description |
256
257
  |--------|------|---------|-------------|
257
258
  | `fitOnLoad` | `boolean` | `true` | Auto-fit drawing to viewport on load |
258
- | `fitPadding` | `number` | `50` | Default padding for fit operations (px) |
259
+ | `fitPadding` | `number` | `0` | Default padding for fit operations (px) |
259
260
 
260
261
  ### Pan Clamping
261
262
 
@@ -368,11 +369,10 @@ These still work but the `on()`/`off()` event emitter is preferred.
368
369
 
369
370
  | Method | Description |
370
371
  |--------|-------------|
371
- | `fit(padding?)` | Fit main drawing to viewport |
372
+ | `fit(opts?)` | Fit main drawing to viewport. Options: `{ animate, padding, duration, easing }` |
372
373
  | `fitToDrawing(id, padding?)` | Fit a specific additional drawing to viewport |
373
374
  | `fitAll(padding?)` | Fit all drawings (main + additional) to viewport |
374
375
  | `panTo(x, y, zoom?)` | Center viewport on world coordinate, optionally set zoom |
375
- | `panToElement(eleOrId, padding?)` | Fit a cytoscape element in viewport (accepts ID string or element) |
376
376
  | `panToRegion(x, y, w, h, padding?)` | Fit a world-coordinate region in viewport |
377
377
 
378
378
  ### Visibility
@@ -440,6 +440,14 @@ These still work but the `on()`/`off()` event emitter is preferred.
440
440
 
441
441
  ## Changelog
442
442
 
443
+ ### 1.3.0
444
+
445
+ - **Breaking**: `fit()` now accepts an options object instead of a padding number: `fit({ animate, padding, duration, easing })`. Calling `fit()` with no arguments still works (uses defaults).
446
+ - **Breaking**: Removed `panToElement()` — use application-level implementation for element navigation with custom zoom/animation logic.
447
+ - **Changed**: Default `fitPadding` changed from `50` to `0`.
448
+ - **Added**: `fit()` now supports `animate` option with configurable `duration` (default 300ms) and `easing` (default `ease-in-out-cubic`).
449
+ - **Added**: `fit()` now calls `cy.resize()` before calculating dimensions.
450
+
443
451
  ### 1.2.3
444
452
 
445
453
  - **Fix**: `_springBackIfNeeded` now respects `panClamp: false`. Previously, setting `panClamp: false` at runtime disabled hard clamping but the soft spring-back animation on mouse release still fired, snapping the viewport back to drawing bounds. This made it impossible to programmatically disable pan clamping (e.g., during `panToElement` animations).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cytoscape-canvas-underlay",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Cytoscape.js plugin for rendering image/PDF canvas underlay behind graph nodes with synchronized zoom and pan",
5
5
  "main": "src/index.js",
6
6
  "module": "src/index.js",
@@ -24,7 +24,7 @@ const DEFAULTS = {
24
24
  // ── Layout ──
25
25
  zIndex: 0, // Canvas z-index within overlay container
26
26
  fitOnLoad: true, // Auto-fit drawing to viewport on load
27
- fitPadding: 50, // Padding (px) for fit operations
27
+ fitPadding: 0, // Padding (px) for fit operations
28
28
 
29
29
  // ── Pan Clamping ──
30
30
  panClamp: false, // Prevent panning too far from drawing bounds
@@ -295,6 +295,15 @@ export class DrawingOverlay {
295
295
  this._enforceLimits();
296
296
  this._draw();
297
297
  if (this._minimap) this._minimap.render();
298
+
299
+ // Schedule high-quality PDF re-render at current zoom level
300
+ const hasPdf = this._main.isPdf || [...this._drawings.values()].some(d => d.isPdf && d.visible);
301
+ if (hasPdf) {
302
+ if (this._qualityTimer) clearTimeout(this._qualityTimer);
303
+ this._qualityTimer = setTimeout(() => {
304
+ this._reRenderAllPdfs().then(() => this._draw());
305
+ }, this.opts.qualityDelay);
306
+ }
298
307
  };
299
308
  this._onResize = () => {
300
309
  this._setupCanvas();
@@ -712,22 +721,31 @@ export class DrawingOverlay {
712
721
  ═══════════════════════════════════════ */
713
722
 
714
723
  /** Fit the main drawing to viewport. */
715
- fit(padding) {
724
+ fit({
725
+ animate = false,
726
+ padding = this.opts.fitPadding,
727
+ duration = 300,
728
+ easing = 'ease-in-out-cubic',
729
+ } = {}) {
716
730
  if (!this._main.w || !this._main.h) return;
717
- const pad = padding ?? this.opts.fitPadding;
718
- const container = this.cy.container().getBoundingClientRect();
719
- const scaleX = (container.width - pad * 2) / this._main.w;
720
- const scaleY = (container.height - pad * 2) / this._main.h;
721
- const zoom = Math.max(this.cy.minZoom(), Math.min(this.cy.maxZoom(),Math.min(scaleX, scaleY)));
731
+ const cy = this.cy;
722
732
 
723
- this.cy.zoom({
724
- level: zoom,
725
- renderedPosition: { x: container.width / 2, y: container.height / 2 },
726
- });
727
- this.cy.pan({
728
- x: (container.width - this._main.w * zoom) / 2,
729
- y: (container.height - this._main.h * zoom) / 2,
730
- });
733
+ cy.resize();
734
+
735
+ const container = cy.container().getBoundingClientRect();
736
+ const cw = container.width - padding * 2;
737
+ const ch = container.height - padding * 2;
738
+ if (cw <= 0 || ch <= 0) return;
739
+
740
+ const scale = Math.min(cw / this._main.w, ch / this._main.h);
741
+ const panX = Math.round(padding + (cw - this._main.w * scale) / 2);
742
+ const panY = Math.round(padding + (ch - this._main.h * scale) / 2);
743
+
744
+ if (animate) {
745
+ cy.animate({ zoom: scale, pan: { x: panX, y: panY }, duration, easing });
746
+ } else {
747
+ cy.viewport({ zoom: scale, pan: { x: panX, y: panY } });
748
+ }
731
749
  }
732
750
 
733
751
  /** Fit a specific additional drawing to viewport. */
@@ -793,34 +811,6 @@ export class DrawingOverlay {
793
811
  });
794
812
  }
795
813
 
796
- /** Pan to center a cytoscape element in viewport. */
797
- panToElement(eleOrId, padding) {
798
- const cy = this.cy;
799
- const ele = typeof eleOrId === 'string' ? cy.getElementById(eleOrId) : eleOrId;
800
- if (!ele || ele.empty?.()) return;
801
-
802
- const bb = ele.boundingBox();
803
- const pad = padding ?? this.opts.fitPadding;
804
- const container = cy.container().getBoundingClientRect();
805
-
806
- // Calculate zoom to fit element
807
- const scaleX = (container.width - pad * 2) / bb.w;
808
- const scaleY = (container.height - pad * 2) / bb.h;
809
- const zoom = Math.max(this.cy.minZoom(), Math.min(this.cy.maxZoom(),Math.min(scaleX, scaleY)));
810
-
811
- const cx = (bb.x1 + bb.x2) / 2;
812
- const cy_ = (bb.y1 + bb.y2) / 2;
813
-
814
- this.cy.zoom({
815
- level: zoom,
816
- renderedPosition: { x: container.width / 2, y: container.height / 2 },
817
- });
818
- this.cy.pan({
819
- x: container.width / 2 - cx * zoom,
820
- y: container.height / 2 - cy_ * zoom,
821
- });
822
- }
823
-
824
814
  /** Pan to a specific region of the drawing. */
825
815
  panToRegion(x, y, w, h, padding) {
826
816
  const pad = padding ?? this.opts.fitPadding;
package/src/index.js CHANGED
@@ -38,11 +38,10 @@ function register(cytoscape) {
38
38
  getRotation() { return overlay.getRotation(); },
39
39
 
40
40
  // ── Navigation ──
41
- fit(padding) { overlay.fit(padding); },
41
+ fit(opts) { overlay.fit(opts); },
42
42
  fitToDrawing(id, padding) { overlay.fitToDrawing(id, padding); },
43
43
  fitAll(padding) { overlay.fitAll(padding); },
44
44
  panTo(x, y, zoom) { overlay.panTo(x, y, zoom); },
45
- panToElement(eleOrId, padding) { overlay.panToElement(eleOrId, padding); },
46
45
  panToRegion(x, y, w, h, padding) { overlay.panToRegion(x, y, w, h, padding); },
47
46
 
48
47
  // ── Visibility ──