JointJS diagram drawn in hidden div has wrong scale - html

I'm rendering a JointJS diagram in a div with visibility hidden. When I set the visibility to visible the shapes in the diagram are massive.
I can see in the code that each node is being scaled by calling:
box = this.node.getBBox()
But this is returning an empty object (presumably because the node is hidden). The width/height are then defaulted to zero. Later on the node is being scaled as follows:
scalable.attr('transform', 'scale(' + (size.width / (scalableBbox.width || 1)) + ',' + (size.height / (scalableBbox.height || 1)) + ')');
Since the returned scalableBbox has width 0 the result of (size.width / (scalableBbox.width || 1)) is the width and the node is therefore scaled to its width * width / 1. The same happens for the height.
This seems wrong!
Is there any way to render a JointJS diagram in an invisible div or is there a way to redraw the diagram once the div becomes visible?

You can remove the scalable class from your element shape to avoid this issue.
The scale feature is only used during resize: documentation
You can define your own shape without scalable element: tutorial

Related

Centering on a specific point when zooming using SVG viewBox with arbitrary initial state

I have a SVG HTML element and I have implemented panning and zooming into it using the mouse. The current implementation of the zooming functionality just multiplies the original width and height of the element by a number that changes when the user scrolls the mouse.
This implementation preserves the origin (0,0) and all other points appear to move closer/further away from it depending on the direction of the zoom.
Intuitively and based o this question. I know, that If I want to zoom in/out on the point the mouse is currently pointing at, I have to pan the viewBox.
I have already looked at the linked questio, as well as two otheres, but I was unable to successfully apply the suggested solutions to my problem. I have also tried to derive the correct formula multiple times, but all my attempts so far have failed.
I am most likely missunderstanding something about the problem and I seem to be unable to generalise the existing answers to my problem.
The following values represent the current state of my viewBox:
offsetX
offsetY
scroll
width
height
I compute the zoomFactor as a function of the scroll variable (Math.exp(scroll/1000)) and set the viewBox property of my SVG as follows: `${offsetX} ${offsetY} ${width * zoomFactor} ${height * zoomFactor}`.
What I am struggling with, is computing the new offsetX and offsetY values based on the previous state and the current position of the mouse inside of the SVG.
processMouseScroll(event: WheelEvent) {
const oldZoomFactor = zoomFactor(this.scroll);
const newZoomFactor = zoomFactor(this.scroll + event.deltaY);
this.scroll = this.scroll + event.deltaY;
this.offsetX = ???;
this.offsetY = ???;
}
How do I compute the new offsets, based on the previous state, so that the when scrolling the mouse, the point bellow it will appear to be stationary?
Thank you for your answers.
I have finally managed to get it working. Turns out, the answer from the first question I found was correct, but my understanding of SVG viewBox was incorrect and I used bad mouse coordinates.
the offset (min-x and min-y; drawn green) of a viewBox is abbsolute and does not depend on the width and height of the viewBox. The mouse coordinates relative to the SVG element (coordinates drawn in black, SVG element drawn in red) are relative to the size of the viewBox. If I enlarge the viewBox, then the part of the picture I can see inside of it shrinks and 100px line drawn by the mouse will cover more of the image.
If we set the size of the dimensions of the viewBox to be the same as the size of the SVG element (initial state), we have a 1:1 scale between the image and the viewBox (the red rectangle would cover the entire image, bordered black). When we make the viewBox smaller we will not fit the entire image into it and therefore the image will appear to be larger.
If we want to compute the absolute position of our mouse in relation to the entire image we can do it like this (same for Y):
position = offsetX + zoomFactor * mouseX (mouseX relative to the SVG element).
When we zoom, we change the factor, but don't change the position of the mouse. If we want the absolute position under the mouse to remain the same, we have to solve the following set of equations:
oldPosition = oldOffsetX + oldZoomFactor * mouseX
newPosition = newOffsetX + newZoomFactor * mouseX
oldPosition = newPosition
we know the mouse position, both zoom factors and the old offset, therefore we solve for the new offset and get:
newOffsetX = oldOffsetX + mouseX * (oldZoomFactor - newZoomFactor)
which is the final formula and very similar to this answer.
Put together we get the final working solution:
processMouseScroll(event: WheelEvent) {
const oldZoomFactor = zoomFactor(this.scroll);
const newZoomFactor = zoomFactor(this.scroll + event.deltaY);
// mouse position relative to the SVG element
const mouseX = event.pageX - (event.target as SVGElement).getBoundingClientRect().x;
const mouseY = event.pageY - (event.target as SVGElement).getBoundingClientRect().y;
this.scroll = this.scroll + event.deltaY;
this.offsetX = this.offsetX + mouseX * (oldZoomFactor - newZoomFactor);
this.offsetY = this.offsetY + mouseY * (oldZoomFactor - newZoomFactor);
}

LeafletJS: How to flip tiles vertically on-the-fly?

Background:
I've produced a 1-terapixel rendering of the Mandelbrot Set and am using LeafletJS to zoom and pan around in it interactively. It works great. But since the Mandelbrot Set is symmetric along the real axis, I'm currently using twice as many tile images as necessary.
Question: How can I hook into LeafletJS's display-time code (using some callback?) so that whenever a tile is loaded via HTTP, it either passes through unchanged or is flipped vertically? This would allow me to reduce the data by many tens of gigabytes on higher zoom levels.
Example: Here are four tiles from zoom level 1 (shown here separated by one pixel). I'd like to throw away the bottom two tile images and load them instead as vertically-flipped versions of the top two tiles. Can this be done on-the-fly with LeafletJS?
More concretely: If I know zoom level z and tile coordinates x,y, I'd like to flip the tile vertically at load-time whenever y is less than 2^(z–1). For instance, at zoom level z=10, I'd like to flip the tiles vertically for all y < 512.
I imagine the answer is going to involve something like setting the transform, -moz-transform, -o-transform, and -webkit-transform properties of the <img> tag to scaleY(-1) and maybe filter and -ms-filter to FlipV, but I don't know where/how to define these in a LeafletJS context.
You would just need to modify the y number of bottom tiles in L.TileLayer._loadTile method, before it gets applied on the image URL.
As for flipping the image itself, unfortunately we cannot use classes because a transform property is already applied by Leaflet directly on the tiles (images), so it overrides any transform in class. Then we have to append any transform, -moz-transform etc. on the tile.style.
L.HalfTileLayer = L.TileLayer.extend({
_loadTile: function (tile, tilePoint) {
tile._layer = this;
tile.onload = this._tileOnLoad;
tile.onerror = this._tileOnError;
this._adjustTilePoint(tilePoint);
//////////////////
var limit = Math.pow(2, tilePoint.z - 1),
y = tilePoint.y;
if (y >= limit) { // modify for bottom tiles, i.e. higher y
tilePoint.y = 2 * limit - y - 1; // y starts at 0
tile.style.transform += " scaleY(-1)"; // append
// apply more transforms for cross-browser
}
/////////////////
tile.src = this.getTileUrl(tilePoint);
this.fire('tileloadstart', {
tile: tile,
url: tile.src
});
}
});
(new L.HalfTileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png')).addTo(map);
Demo: http://jsfiddle.net/ve2huzxw/73/
Note that in the default configuration, y = 0 is the top, y = 2^z - 1 is the bottom.

How do I correctly translate my map after scaling?

I'm creating a camera in canvas like the one in Super Smash Bros, where the center of the camera follows the center point of all players and scales to encompass all the players.
I have it set up to find the distance between the 2 players, and if it's larger than the canvas size, the camera scale lowers to decrease the size of the blocks, player sprites, etc.
ctx.scale(cameraS,cameraS);
ctx.translate(-(cameraX*cameraS)+(CANVAS_WIDTH/2),-(cameraY*cameraS)+(CANVAS_HEIGHT/2));
These are what scale and move the drawn images to a position relative to the screen.
This is the actual game using the code and as you can tell, the scaling and moving of the images is slightly incorrect, but I'm not sure why!
https://dl.dropboxusercontent.com/u/51784213/Conjugate/index.html
For reference, the red dot is the position centered between both players. The lines show the dead center of the actual canvas. When scaling is 1(no scaling at all), the red dot is completely centered as it should be. When the scaling starts to decrease, the red dot begins to move off center in weird directions.
For the code to be working correctly, the dot should be centered at all times, even during the scaling process!
Transformations are applied in the reverse order; so you are first translating and then scaling. This means that for a point (x, y), after the current transformation, you get
(
(x + CANVAS_WIDTH/2 - cameraX*cameraS) * cameraS,
(y + CANVAS_HEIGHT/2 - cameraY*cameraS) * cameraS
)
What's actually needed here is the canvas be translated by scaled (cameraX, cameraY) and then be offset by actual (CANVAS_WIDTH/2, CANVAS_HEIGHT/2), so that (cameraX, cameraY) is at center of the visible canvas.
Or rather, the transformation needed here for a point (x, y) is
(
(x - cameraX) * cameraS + CANVAS_WIDTH/2,
(y - cameraY) * cameraS + CANVAS_HEIGHT/2
)
Hence, the code becomes, if you choose to apply translate first,
ctx.scale(cameraS,cameraS);
ctx.translate(-cameraX+CANVAS_WIDTH/(2*cameraS),-cameraY+CANVAS_HEIGHT/(2*cameraS));
Or, if you choose to apply scaling first
ctx.translate(-cameraX*cameraS + CANVAS_WIDTH/2, -cameraY*cameraS + CANVAS_HEIGHT/2);
ctx.scale(cameraS, cameraS);
Working JSFiddle.

Auto-shrinking HTML content

Is there an easy way, or existing library, which will shrink the content of a div if it overflows it's parent? I am envisioning something like Powerpoint / Keynote, where text in the main box shrinks in size automatically, when the content gets to big (so not just re-formatting, but the fonts and pictures get smaller to fit in the div).
I feel this should be simple to do, using CSS 'scale', but I am surprised I can't find someone else who has done it, so I wonder if there is an issue I am not thinking of?
Something like this? I set up a quick test that calculates the ratio of child to parent boxes and sets an appropriate scale to make the widths match:
var one = $('.one');
var two = $('.two');
if (two.width() > one.width()) {
two.css({
'transform': 'scale(' + one.width() / two.width() + ')',
'transform-origin': '0 0'
});
}

Why do we divide by 2 getContentSize?

In sample from Cocos2d-x Tutorials when we describe actionMove
CCFiniteTimeAction* actionMove =
CCMoveTo::create( (float)actualDuration,
ccp(0 - target->getContentSize().width/2, actualY) );
we set a point ccp(0 - target->getContentSize().width/2, actualY). If we have target with 20 width then we have point (-10, actualY), and half target must be visible, but it's not so. Why?
Initial target position
target->setPosition(
ccp(winSize.width + (target->getContentSize().width/2),
actualY) );
Here we also divide by 2, but I understand it (winSize.width + any number and target becomes invisible, outside the screen).
Cocos2d-x uses the center of the object as the origin/anchor point, rather than the corner. So if you want half of your object to be visible on the edge of the screen, use:
ccp(0, actualY)
or
ccp(winSize.width)
You add half of the contentSize if you want the object to be completely off the screen.