Layout centered on one node - cytoscape.js

Is there a way to require that a layout doesn't change the position of one "root" node while considering it during the algorithm ? Or equivalently, is there a way to always center the camera to this node/keep the camera at the same relative position to this node ?
A bit of context. I am working on an iteratively built graph. Each time parts are added to the graph, the layout is completed. The graph may grow too big to be printed on a screen, and alternatives to the fit options are welcome. What is important though, is that the user is able to follow the node he selected. The best would be that this node doesn't move.

Here is an outline of the general approach. It can be applied with whatever strategy you like re. zoom and pan. The main thing is that you need to know the start and end positions of each node. So you run the layout in a batch, creating a visual nop, and then run a preset layout with the desired zoom and pan.
const clone = obj => Object.assign({}, obj);
const savePos1 = n => n.scratch('_layoutPos1', clone(n.position()));
const savePos2 = n => n.scratch('_layoutPos2', clone(n.position()));
const restorePos1 = n => n.position(n.scratch('_layoutPos1'));
const getPos2 = n => n.scratch('_layoutPos2');
cy.startBatch();
const nodes = cy.nodes();
const layout = cy.layout(myLayoutOptions); // n.b. animate:false
const layoutstop = layout.promiseOn('layoutstop');
nodes.forEach(savePos1);
layout.run();
await layoutstop;
nodes.forEach(savePos2);
nodes.forEach(restorePos1);
cy.endBatch();
cy.layout({
name: 'preset',
animate: true,
positions: getPos2,
// specify zoom and pan as desired
zoom,
pan
}).run();

Related

Does pdf.js allow rendering of a selected (rectangular) part of a page instead of rendering an entire page to a canvas?

Does pdf.js allow to render a PDF page only partially? More specifically, is it possible to tell pdf.js to render a selected "rectangle of pixels" out of an entire PDF page?
Assuming a resolution of 144 dpi, a typical page (DIN A4) would have approx. 684 (width) by 1190 (height) pixels. I would like to render (for example) a rectangle like [100, 100] (top left coordinate in pixels) and [400, 400] (bottom right coordinate in pixels).
A typical use case could be a scanned document with several handwritten notes that I would like to display and further process individually.
I do understand that a "workaround" could be to save the entire page as jpg (or any other suitable bitmap format) and apply some clipping function. But this would for sure be a less performant approach than selected rendering.
pdfs.js uses a viewport object (presumably containing parameters) for rendering. This object contains
height
width
offsetX
offsetY
rotation
scale
transform
viewBox (by default [0, 0, width / scale, height / scale])
One might think that manipulating the viewBox inside it might lead to the desired outcome, but I have found that changing the viewBox parameters does not do anything at all. The entire page is rendered every time that I apply the render method.
What might I have done wrong? Does pdf.js offer the desired functionality? And if so, how can I get it to work? Thank you very much!
Here is a very simple React component demonstrating my approach (that does not work):
import React, { useRef } from 'react';
import { pdfjs } from 'react-pdf';
pdfjs.GlobalWorkerOptions.workerSrc = 'pdf.worker.js';
function PdfTest() {
// useRef hooks
const myCanvas: React.RefObject<HTMLCanvasElement> = useRef(null);
const test = () => {
const loadDocument = pdfjs.getDocument('...');
loadDocument.promise
.then((pdf) => {
return pdf.getPage(1);
})
.then((page) => {
const viewport = page.getViewport({ scale: 2 });
// Here I modify the viewport object on purpose
viewport.viewBox = [100, 100, 400, 400];
if (myCanvas.current) {
const context = myCanvas.current.getContext('2d');
if (context) {
page.render({ canvasContext: context, viewport: viewport });
myCanvas.current.height = viewport.height;
myCanvas.current.width = viewport.width;
}
}
});
};
// Render function
return (
<div>
<button onClick={test}>Test!</button>
<canvas ref={myCanvas} />
</div>
);
}
export default PdfTest;
My initial thought was also to modify a viewBox of page Viewport. This was not the right guess (I hope that you already figured it out).
What do you need really to do to project only a part of a page to canvas is to prepare correctly the transformation of Viewport.
So it will look more or less like following:
const scale = 2
const viewport = page.getViewport({
scale,
offsetX: -100 * scale,
offsetY: - 100 * scale
})
This will move your your box section to the beginning of the canvas coordinates.
What probably you would like to do next is to make a canvas equal to the selected rectangle size (in your case is 300x300 scaled by your scale) and this solved the issue in my case.

lazyloading swiper.js preloading the next image?

Is there a way to preload the next or two next images with a swiper.js while lazyloading is active?
The slider contains 100 images which we don’t want to load while the page opens for obvious reasons but we want the first and the two next images to be loaded to have them present within the next slide while the animation runs.
any ideas?
I am looking for the same thing, and looking at the swiperJS documentation, I stumbled upon the API settings for lazyloading.
Looks like maybe loadPrevNext and loadPrevNextAmount might help us out.
loadPrevNext (boolean, default:false)
Set to true to enable lazy loading for the closest slides images (for previous and next slide images)
(I think they actually mean preload next and previous image, if I check other examples and demo's)
loadPrevNextAmount (number, default: 1)
Amount of next/prev slides to preload lazy images in. Can't be less than slidesPerView
So:
const swiper = new Swiper('.swiper-container', {
lazy: {
loadPrevNext: true, // pre-loads the next image to avoid showing a loading placeholder if possible
loadPrevNextAmount: 2 //or, if you wish, preload the next 2 images
},
});
Since version 9, Swiper does not have a lazy loading API and instead leverages browsers' native lazy loading functionality:
Since version 9 Swiper doesn't have specifid lazy loading API, as it relies on native browser lazy loading feature. To use lazy loading, we just need to set loading="lazy" on images and add preloader element
loadPrevNextAmount is gone from the API. One way to achieve similar behavior is to use the slideChange event to force the next N images to load by setting their loading attribute to eager (assuming they were originally set as lazy and each slide element has a single image you want to preload):
const swiper = new Swiper('.swiper', { ... });
const preloadNext = (n) => {
swiper
.slides
.slice(swiper.activeIndex, swiper.activeIndex + n + 1)
.map(slide => slide.querySelector('img'))
.forEach(s => s.setAttribute('loading', 'eager'));
};
// preload the next 2 images immediately
preloadNext(2);
// preload the next 2 images after changing slides
swiper.on('slideChange', () => preloadNext(2));
Another possible optimization is to wait for the page to fully load, which includes the first image in the swiper, and only then preload the following images. This avoids loading the first 3 images at the same time in order to display the first image faster:
let swiper;
const preloadNext = (n) => {
swiper
.slides
.slice(swiper.activeIndex, swiper.activeIndex + n + 1)
.map(slide => slide.querySelector('img'))
.forEach(s => s.setAttribute('loading', 'eager'));
};
document.addEventListener('DOMContentLoaded', () => {
swiper = new Swiper('.swiper', { ... });
swiper.on('slideChange', () => preloadNext(2));
});
window.addEventListener('load', () => {
preloadNext(2);
});

How can I get the H3 hexagons on a react-map-gl/deck.gl viewport?

I want to query for data based on the H3 hexagons that are visible on the viewport (and for new data on each viewport change). Is there anyway to achieve this with react-map-gl and deck.gl?
To get the hexagons inside the viewport, you need to get the bounding box of the current viewport. If you have the current viewport as {latitude, longitude, zoom, width, height} (which you probably have in your component state if you're using react-map-gl), you can get the viewport using viewport-mercator-project:
import WebMercatorViewport from 'viewport-mercator-project';
function bboxFromViewport(viewport) {
const {width, height} = viewport;
const projection = new WebMercatorViewport(viewport);
const [west, north] = projection.unproject([0, 0]);
const [east, south] = projection.unproject([width, height]);
return {north, south, east, west};
}
Then you can use the bounding box with h3.polyfill to get the list of contained hexagons at a given resolution:
const nw = [north, west];
const ne = [north, east];
const sw = [south, west];
const se = [south, east];
const hexes = h3.polyfill([nw, ne, se, sw], resolution);
Depending on your use case, you might want to expand the bounding box before calling polyfill, to get additional data outside the immediate viewport.
You also probably want to bound this on the viewport extent somehow, or you could end up with millions of hexagons on zoom out. One cheap hack I've used for this is to take a very rough estimate of the number of hexagons we'll get, and avoid calling polyfill if it's too high:
// Inexact, but it doesn't matter for our purposes
const KM_PER_DEGREE_LAT = 111.2;
function estimateHexagonsInBBox(bbox, width, height, res) {
// This is an extremely rough estimate, but we're just trying
// to get a reasonable order of magnitude
const aspect = width / height;
const latKm = (bbox.north - bbox.south) * KM_PER_DEGREE_LAT;
const lonKm = latKm * aspect;
return (latKm * lonKm) / h3.hexArea(res, h3.UNITS.km2);
}

How to improve PanResponder efficiency in ReactNative?

I am using React Native for a game (perhaps not the best framework but not the point). I need user to be able to slide on a grid. On the pic, user is selecting MKEC without removing its finger from screen.
I am from the web, so I thought I would use onHover event. But it dossn't exist on mobile. So I created a parent component and a grid component. Parent component catch event with PanResponder, and I calculate where user finger is on the grid, and change state of the grid component.
However, if user is going too fast from on letter to another, it sometimes 'skip' a letter. If you go from M to G (first column) very fast, it seems that not event is triggered on H. Am I clear ? User feels latency.
componentWillMount() {
const onRelease = (evt, gestureState) => {
this.props.validateWord();
};
const onMove = (evt, gestureState: GestureState) => {
const obj = Object.assign({}, {
pageX: evt.nativeEvent.pageX,
pageY: evt.nativeEvent.pageY
});
const pos = {
x: evt.nativeEvent.pageX - this.position.pageX,
y: evt.nativeEvent.pageY - this.position.pageY
}
const whatSquare = whatSquareIsTouched(
pos,
defaultLetter.width, // size of letter
defaultLetter.width * 0.22 // padding for diagonale
);
const letter = this.props.gridReducer[whatSquare.y]
&& this.props.gridReducer[whatSquare.y][whatSquare.x] ?
this.props.gridReducer[whatSquare.y][whatSquare.x] : null;
// if letter => if touch is on a letter and not outside of grid comp
if (letter) {
this.props.letterTouched(letter, whatSquare.insidePadding);
}
}
this._panResponder = createResponder(onMove, onRelease);
}
How could I improve my code ? Or perhaps go another way to tackle this problem?
Update after comment :
I have not solved the problem yet. But i tried the following. I assumed that when user change direction, a new event is trigger. So I could calculate the finger path. If user starts at M and finish in G without changing direction, then H has been hovered.

How to avoid camera jumps during/after Tween/animation?

I'm having some trouble while creating a camera Tween in THREE.js, specifically at the end and beginning of the animation, there always seems to be a camera 'jump', meaning that the camera flickers once the animation starts and once the animation ends. For reference, what I'm doing is :
Camera is overlooking a scenario from above.
When user clicks on an element of the scenario, the camera zooms on it (by a TWEEN) and when it's close enough, the OrbitControls target change to the centroid of the selected element, and autorotate starts, so the user sees the element rotating in the center of the screen.
When user clicks again, the camera zooms out to its initial position (by a TWEEN) and goes back to the original controls.
I'm experiencing 'jumps/flickers' at the beginning and end of each TWEEN.
This is my tween function :
var origpos = new THREE.Vector3().copy(camera.position); // original position
var origrot = new THREE.Euler().copy(camera.rotation); // original rotation
camera.position.set(x, y+500, z+variation);
camera.lookAt(new THREE.Vector3(x,y,z));
var dstrot = new THREE.Euler().copy(camera.rotation)
// reset original position and rotation
camera.position.set(origpos.x, origpos.y, origpos.z);
camera.rotation.set(origrot.x, origrot.y, origrot.z);
options = {duration: 3000};
//
// Tweening
//
// position
new TWEEN.Tween(camera.position).to({
x: x,
y: y+500,
z: z
}, options.duration).easing(TWEEN.Easing.Cubic.Out).onUpdate(function () {
camera.lookAt(new THREE.Vector3(x,y,z));
}).onComplete(function () {
controls.autoRotate = true;
controls.autoRotateSpeed = 5.0;
controls.target = new THREE.Vector3(x, y, z);
}).start();
// rotation (using slerp)
(function () {
var qa = camera.quaternion; // src quaternion
var qb = new THREE.Quaternion().setFromEuler(dstrot); // dst quaternion
var qm = new THREE.Quaternion();
var o = {t: 0};
new TWEEN.Tween(o).to({t: 1}, options.duration).onUpdate(function () {
THREE.Quaternion.slerp(qa, qb, qm, o.t);
camera.quaternion.set(qm.x, qm.y, qm.z, qm.w);
}).start();
}).call(this);
OK, it seems that the issue was in itself with the method I was using to rotate the camera, using quaternions and SLERP.
I found that the best way (easier and without flicker/jump) would be to interpolate the parameters of camera.rotation instead of interpolating the quaternions.
So, a Tween of camera.rotation.the_axis_where_you_want_to_rotate works perfectly, and done concurrently with a Tween on the position, achieves the effect I was looking for.