cytoscape-canvas-underlay 1.2.4 → 1.3.1

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.4",
3
+ "version": "1.3.1",
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
@@ -207,6 +207,9 @@ export class DrawingOverlay {
207
207
  // Additional drawings
208
208
  this._drawings = new Map();
209
209
 
210
+ // PDF 존재 여부 캐시 (zoom/pan마다 순회 방지)
211
+ this._hasPdfCached = false;
212
+
210
213
  // RAF / debounce
211
214
  this._rafId = null;
212
215
  this._qualityTimer = null;
@@ -297,7 +300,7 @@ export class DrawingOverlay {
297
300
  if (this._minimap) this._minimap.render();
298
301
 
299
302
  // 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);
303
+ const hasPdf = this._hasPdfCached;
301
304
  if (hasPdf) {
302
305
  if (this._qualityTimer) clearTimeout(this._qualityTimer);
303
306
  this._qualityTimer = setTimeout(() => {
@@ -649,6 +652,7 @@ export class DrawingOverlay {
649
652
  }
650
653
  }
651
654
  this._loading = false;
655
+ this._updateHasPdf();
652
656
 
653
657
  if (this.opts.fitOnLoad) this.fit();
654
658
  this._draw();
@@ -721,22 +725,31 @@ export class DrawingOverlay {
721
725
  ═══════════════════════════════════════ */
722
726
 
723
727
  /** Fit the main drawing to viewport. */
724
- fit(padding) {
728
+ fit({
729
+ animate = false,
730
+ padding = this.opts.fitPadding,
731
+ duration = 300,
732
+ easing = 'ease-in-out-cubic',
733
+ } = {}) {
725
734
  if (!this._main.w || !this._main.h) return;
726
- const pad = padding ?? this.opts.fitPadding;
727
- const container = this.cy.container().getBoundingClientRect();
728
- const scaleX = (container.width - pad * 2) / this._main.w;
729
- const scaleY = (container.height - pad * 2) / this._main.h;
730
- const zoom = Math.max(this.cy.minZoom(), Math.min(this.cy.maxZoom(),Math.min(scaleX, scaleY)));
735
+ const cy = this.cy;
731
736
 
732
- this.cy.zoom({
733
- level: zoom,
734
- renderedPosition: { x: container.width / 2, y: container.height / 2 },
735
- });
736
- this.cy.pan({
737
- x: (container.width - this._main.w * zoom) / 2,
738
- y: (container.height - this._main.h * zoom) / 2,
739
- });
737
+ cy.resize();
738
+
739
+ const container = cy.container().getBoundingClientRect();
740
+ const cw = container.width - padding * 2;
741
+ const ch = container.height - padding * 2;
742
+ if (cw <= 0 || ch <= 0) return;
743
+
744
+ const scale = Math.min(cw / this._main.w, ch / this._main.h);
745
+ const panX = Math.round(padding + (cw - this._main.w * scale) / 2);
746
+ const panY = Math.round(padding + (ch - this._main.h * scale) / 2);
747
+
748
+ if (animate) {
749
+ cy.animate({ zoom: scale, pan: { x: panX, y: panY }, duration, easing });
750
+ } else {
751
+ cy.viewport({ zoom: scale, pan: { x: panX, y: panY } });
752
+ }
740
753
  }
741
754
 
742
755
  /** Fit a specific additional drawing to viewport. */
@@ -802,34 +815,6 @@ export class DrawingOverlay {
802
815
  });
803
816
  }
804
817
 
805
- /** Pan to center a cytoscape element in viewport. */
806
- panToElement(eleOrId, padding) {
807
- const cy = this.cy;
808
- const ele = typeof eleOrId === 'string' ? cy.getElementById(eleOrId) : eleOrId;
809
- if (!ele || ele.empty?.()) return;
810
-
811
- const bb = ele.boundingBox();
812
- const pad = padding ?? this.opts.fitPadding;
813
- const container = cy.container().getBoundingClientRect();
814
-
815
- // Calculate zoom to fit element
816
- const scaleX = (container.width - pad * 2) / bb.w;
817
- const scaleY = (container.height - pad * 2) / bb.h;
818
- const zoom = Math.max(this.cy.minZoom(), Math.min(this.cy.maxZoom(),Math.min(scaleX, scaleY)));
819
-
820
- const cx = (bb.x1 + bb.x2) / 2;
821
- const cy_ = (bb.y1 + bb.y2) / 2;
822
-
823
- this.cy.zoom({
824
- level: zoom,
825
- renderedPosition: { x: container.width / 2, y: container.height / 2 },
826
- });
827
- this.cy.pan({
828
- x: container.width / 2 - cx * zoom,
829
- y: container.height / 2 - cy_ * zoom,
830
- });
831
- }
832
-
833
818
  /** Pan to a specific region of the drawing. */
834
819
  panToRegion(x, y, w, h, padding) {
835
820
  const pad = padding ?? this.opts.fitPadding;
@@ -910,6 +895,16 @@ export class DrawingOverlay {
910
895
  Public API: Utility
911
896
  ═══════════════════════════════════════ */
912
897
 
898
+ /** PDF 존재 여부 캐시 갱신 */
899
+ _updateHasPdf() {
900
+ this._hasPdfCached = this._main.isPdf;
901
+ if (!this._hasPdfCached) {
902
+ for (const d of this._drawings.values()) {
903
+ if (d.isPdf && d.visible) { this._hasPdfCached = true; break; }
904
+ }
905
+ }
906
+ }
907
+
913
908
  /** Force redraw. */
914
909
  refresh() {
915
910
  this._setupCanvas();
@@ -1063,6 +1058,7 @@ export class DrawingOverlay {
1063
1058
  }
1064
1059
 
1065
1060
  this._drawings.set(id, state);
1061
+ this._updateHasPdf();
1066
1062
  this._draw();
1067
1063
  this._emit('drawingAdd', id, { x: state.x, y: state.y, width: state.width, height: state.height });
1068
1064
  }
@@ -1078,6 +1074,7 @@ export class DrawingOverlay {
1078
1074
  const state = this._drawings.get(id);
1079
1075
  if (!state) return;
1080
1076
  state.visible = visible;
1077
+ if (state.isPdf) this._updateHasPdf();
1081
1078
  this._draw();
1082
1079
  }
1083
1080
 
@@ -1088,12 +1085,14 @@ export class DrawingOverlay {
1088
1085
 
1089
1086
  removeDrawing(id) {
1090
1087
  this._drawings.delete(id);
1088
+ this._updateHasPdf();
1091
1089
  this._draw();
1092
1090
  this._emit('drawingRemove', id);
1093
1091
  }
1094
1092
 
1095
1093
  clearDrawings() {
1096
1094
  this._drawings.clear();
1095
+ this._updateHasPdf();
1097
1096
  this._draw();
1098
1097
  }
1099
1098
 
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 ──