raphael js, resize canvas then setViewBox to show all elements - svg

I have a problem with my canvas.
My canvas in first width = 1300, height = 500
Then I resize it to width = 800px, height = 500
I try setViewBox to zoom it. But mouse not fix with element when I drag them.
#canvas.resize(800, 500)
#canvas.setViewBox(0,0, ??, ??)
how to calculate it???
Thank for your help. :)

You can calculate the necessary dimensions using an approach like this:
function recalculateViewBox( canvas )
{
var max_x = 0, max_y = 0;
canvas.forEach( function( el )
{
var box = el.getBBox();
max_x = Math.max( max_x, box.x2 );
max_y = Math.max( max_y, box.y2 );
} );
if ( max_x && max_y )
canvas.setViewBox( 0, 0, max_x, max_y );
}
Essentially, you simply walk the contents of the canvas and construct a meta-bounding box, then adjust the viewBox to it.
If you wanted to get a little fancy, you could always animate the viewbox so that it transitions fluidly to it's new size. Not functionally important, but moderately sexy...

Related

Resize image keeping aspect ratio and fit in bounds nodejs

I need to resize images to fit in a specific dimensions. I want to keep aspect ratio.
For example
original image:
w:634
h:975
resize to max:
w:50
h:100
result:
w:50
h:85
I have not found anything that could do that(calculations for w and h)
and I am too dumb to figure it out by myself
copilot suggested me something that just keeps aspect ratio
If you want to use packages. I prefer jimp for image editing.
Jimp.read('image.jpg')
.then((lenna) => {
const isHorizontal = lenna.getWidth() > lenna.getHeight();
const ratio = isHorizontal
? lenna.getWidth() / lenna.getHeight()
: lenna.getHeight() / lenna.getWidth();
const width = 375; // set the width you want
const height = isHorizontal ? width / ratio : width * ratio;
return lenna.resize(width, height).quality(60).write("image.jpg");
})
.catch((err) => {
console.error(err);
});
Calculate the aspect ratio of your original image and of your maximum dimensions. According to that ratios, either take the maximum width or height as fixed and calculate the other side accordingly.
let
oheight = 634, owidth = 975,
mheight = 50, mwidth = 100,
theight, twidth;
let
oratio = owidth/oheight, // (~1.54)
mratio = mwidth/mheight, // (2)
if (mratio > oratio) {
//original image is "higher" so take the maximum height
//and calculate the width accordingly
theight = mheight; //50
twidth = theight * oratio; //77
} else {
//original image is "wider" so take the maximum width
//and calculate the height accordingly
twidth = mwidth;
theight = twidth / oratio;
}
But any decent image processing library will have the functionality, that you can pass in the maximum dimensions and define to keep the aspect ratio and will do these calculations internally ...

Why does the alpha value change when I have given it a constant value?

I am using the p5.js Web Editor
var sketch = function (p) {
with(p) {
p.setup = function() {
createCanvas(400, 400);
secCanvas = createGraphics(400, 400);
secCanvas.clear();
trans = 0;
drop_size = 10;
sun_size = 50;
radius = 10;
};
p.draw = function() {
background(3, 182, 252, 1);
image(secCanvas, 0, 0)
secCanvas.fill(255, 162, 0, 1)
secCanvas.ellipse(width/2, 0 + sun_size, sun_size)
fill(40, trans)
trans = random(255);
ellipse(random(mouseX + radius, mouseX - radius), random(mouseY + radius, mouseY - radius), drop_size)
drop_size = random(50)
};
}
};
let node = document.createElement('div');
window.document.getElementById('p5-container').appendChild(node);
new p5(sketch, node);
body {
background-color:#efefef;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
<div id="p5-container"></div>
When I set a discrete value of alpha in secCanvas.fill(). The value appears to be gradually increase(and stops soon), while I gave no such instruction. Why is this happening? This only happens when I put background(3, 182, 252, 1); in the draw function but not when I put it in the setup function.
Each frame is drawn on top of all previous frames, so when you draw a semi-transparent background, you can still see the previous frames underneath it.
Think of it as adding a very thin coat of paint over top what you've already painted. Because the color you're adding is semi-transparent, you can still see what's underneath it. Then during the next frame, you add another layer of paint, and the previous frames get just a little more faint.
They stop becoming more faint because of the way the computer calculates the new color, based on the previous frames and the new semi-transparent background color. Long story short, the color you're drawing is almost 100% transparent, so it's not strong enough to completely hide previous frames.

How to stretch geometry so its bounding box fits precisely the screen in Three.js

I am looking for the way of stretching a geometry (with all vertices z = 0) into visible screen (HTML Canvas Element).
For now I have worked out how to fit the geometry to the screen, like this:
with following code that basically adjusts camera.z to fit geometry to the height of canvas.
geometry.computeBoundingBox();
const bbox = geometry.boundingBox;
const geometryCenter = bbox.getCenter(new THREE.Vector3());
const geometrySize = bbox.getSize(new THREE.Vector3())
const cameraZ = getZFromGeometrySize(camera.fov, geometrySize);
const scale = getScaleFromZ(height, camera.fov, cameraZ);
const zoomTransform = d3.zoomIdentity
.translate(width * 0.5, height * 0.5)
.scale(scale);
zoom.transform(canvasSelection, zoomTransform);
camera.position.set(geometryCenter.x, geometryCenter.y, cameraZ)
camera.updateProjectionMatrix();
with below definitions of functions:
function getZFromGeometrySize(fov, geometrySize) {
const maxSize = Math.max( geometrySize.x, geometrySize.y );
const halfFOVRadians = toRadians(fov * 0.5);
return maxSize / ( 2 * Math.tan( halfFOVRadians ) );
}
function getScaleFromZ (height, fov, z) {
const halfFOVRadians = toRadians(fov * 0.5);
return height / (2 * Math.tan(halfFOVRadians) * z);
}
This however is using camera position so geometry will fit the view. However, I am looking for the way to stretch the geometry so its bounding box precisely fits the screen, ideally with some predefined padding.
Since this is not related to camera settings I need to manipulate geometry vertices values to stretch it horizontally. How to achieve this? I want to retain values of vertices as they relate to underlying data.
I assume this would need to be a function of canvas dimensions (width, height), geometry coordinates, and camera settings returning new geometry coordinates? Any hint is appreciated.
A short answer to this question is: to set camera's aspect ratio to 1.0.
This will work if geometry bounds are in clip space already [-1, 1 ]. If not they have to be converted to clip space first.

Can I make SVG elements selectable in the browser?

I am attempting to write a graphical grid editor and I was looking at the possibility of use SVG to draw the grid, with the hope that there is some was to select the grid elements. So, the SVG grid would be made up of colored rectangles arranged in columns and rows. User can draw digital pictures by coloring different rectangles different colors.
I can easily draw a grid of svg rects and display is fine. But I want the user to be able to select a set of rects from the svg display. So, perhaps they want to select multiple rects by dragging a rectangular region with the mouse and selecting them, then they might want to color all a certain color.
Is there a way for the browser to show selection of a subset of rects displayed in my grid? Or is that not a possibility with SVG? I am new to SVG, so have never worked with it before. My simple grid test, does not show any selection when dragging over the svg rect elements with the mouse.
Is there some easy way to do this?
Alternatively, I think I would need to use an HTML 5 canvas for display and handle all the mouse events myself.
This is how I would do it:
I create the grid and I'm saving the rects in the rects array.
On mouse down I change the value of the min_x and min_y variables.
On mouse up I change the value of the max_x and max_y, and filter the rects array to change the color of the rects in the selected range:
x >= min_x-size &&
y >= min_y-size &&
x <= max_x &&
y <= max_y
This is an example. Please click and drag over the svg canvas.
let SVG_NS = svg.namespaceURI;
let size = 10;// the size of a grid cell
let w = 100;//the width of the grid
let h = 100;//the height of the grid
let rectx=0,recty=0;
let selecting = false;
// the rects array
let rects = [];
//create the grid.Push the new rect into the rects array. All the recta have a fill attribute
for(let y = 0; y< h; y+=size){
for(let x = 0; x < w; x+=size){
let rect = drawSVGelmt({x:x,y:y,width:size,height:size,fill:"white"},"rect", svgG);
rects.push(rect)
}
}
let min_x = 0,max_x=100,min_y=0,max_y = 100
//on mouse down change the value of the min_x and min_y
svg.addEventListener("mousedown",(e)=>{
selecting = true
m = oMousePosSVG(e,svg)
min_x = m.x,min_y=m.y;
rectx = m.x;
recty = m.y;
selector.setAttributeNS(null,"x",rectx);
selector.setAttributeNS(null,"y",recty);
})
//on mouse up change the value of the max_x and max_y, filter the rects array and change the color of the "selected" rects
svg.addEventListener("mousemove",(e)=>{
if(selecting){
m = oMousePosSVG(e,svg);
selector.setAttributeNS(null,"width",m.x-rectx);
selector.setAttributeNS(null,"height",m.y-recty);
}
});
svg.addEventListener("mouseup",(e)=>{
if(selecting){
let m = oMousePosSVG(e,svg)
max_x = m.x,max_y=m.y;
selector.setAttributeNS(null,"x",0);
selector.setAttributeNS(null,"y",0);
selector.setAttributeNS(null,"width",0);
selector.setAttributeNS(null,"height",0);
rects.filter((el)=> {
let x = el.getAttribute("x");
let y = el.getAttribute("y");
if (x >= min_x-size &&
y >= min_y-size &&
x <= max_x &&
y <= max_y){
el.setAttribute("fill","red")}
});
}
selecting = false;
})
// a function to draw a new svg element
function drawSVGelmt(o,tag, parent) {
let elmt = document.createElementNS(SVG_NS, tag);
for (let name in o) {
if (o.hasOwnProperty(name)) {
elmt.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(elmt);
return elmt;
}
// a function to detect the mouse position on the svg canvas
function oMousePosSVG(e, svg) {
var p = svg.createSVGPoint();
p.x = e.clientX;
p.y = e.clientY;
var ctm = svg.getScreenCTM().inverse();
var p = p.matrixTransform(ctm);
return p;
}
svg{border:1px solid; width:90vh;}
rect{stroke:black; vector-effect:non-scaling-stroke;pointer-events:all}
<svg id="svg" viewBox="0 0 100 100">
<g id="svgG"></g>
<rect id="selector" stroke="#ccc" fill="rgba(0,0,0,.2)" />
</svg>

D3.js semantic zoom misbehaving

I've been trying to teach myself D3.js, but I can't seem to get semantic zoom (zooming positions but not shapes) to work for me.
I've read the d3 zoom docs here, and attempted to functionally copy the svg semantic zoom example code
This is my code:
var X, Y, circle, circles, h, i, j, svg, transform, w, zoom, _i, _j;
w = 1200;
h = 600;
circles = [];
for (j = _i = 0; _i <= 6; j = ++_i) {
for (i = _j = 0; _j <= 12; i = ++_j) {
circles.push({r: 25, cx: i * 50, cy: j * 50});
}
}
X = d3.scale.linear()
.domain([0, 1])
.range([0, 1]);
Y = d3.scale.linear()
.domain([0, 1])
.range([0, 1]);
zoom = d3.behavior.zoom()
.x(X)
.y(Y)
.on("zoom", function() {
return circle.attr("transform", transform);
});
transform = function(d) {
return "translate(" + (X(d.cx)) + ", " + (Y(d.cy)) + ")";
};
svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.call(zoom)
.append("g");
circle = svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.attr("r", function(d) {
return d.r;
}).attr("cx", function(d) {
return d.cx;
}).attr("cy", function(d) {
return d.cy;
}).attr("transform", transform);
Live version at jsfiddle.
This should be pretty simple. I'm creating grid of circles that should exactly touch when no zoom is applied (distance is 50 px, diameter is 50 px). When I zoom in, I expect the circles to spread apart, with the point under the mouse remaining stationary. I expect the zoom to be smooth and linear with applied mouse wheeling. The circles should remain the same size, though, so that they stop touching when I zoom in; they should overlap when I zoom out.
Instead, initially, the circles are spread out exactly twice as far as they should be. When I zoom in and out, the center point is not under the mouse (and moves around depending on how I pan). Zoom is highly nonlinear, asymptotically approaching a scale of 1 (circles touching) as I zoom out, and rapidly accelerating as I zoom in.
This seems really odd, and I can't spot significant differences between my code and the semantic zoom example, which works as expected. I conclude that I don't actually understand how D3 zoom is supposed to work. Can someone sort me out?
Your code is very close to being correct: Working demo.
Use scale to map the location of objects
Instead of saving the exact location of objects in them and then using scales with range and domain set to [0, 1], use the scales to do the mapping for you:
for (j = _i = 0; _i <= 6; j = ++_i) {
for (i = _j = 0; _j <= 12; i = ++_j) {
circles.push({
r: 25,
cx: i,
cy: j,
color: "#000"
});
}
}
X = d3.scale.linear()
.domain([0, 6])
.range([0, w]);
Y = d3.scale.linear()
.domain([0, 12])
.range([0, h]);
The change here is that now D3 knows about the aspect ratio of your viewport and in what proportions it should transform the scales so as to keep the point under the svg static under the mouse. Otherwise, it was trying to zoom in and out of a square, resulting in a jarring experience.
The problem was the initial position of the circles stacking up on the translation.
Live code with the problem pointed out and fixed, and a few other modifications:
var size = 600
var scale = 100
circles = []
for (var j = 0; j<6; j++) {
for (var i = 0; i<6; i++) {
circles.push({x: i*scale, y: j*scale })
}
}
var X = d3.scale.linear()
.domain([0,6*scale])
.range([0,size])
var Y = d3.scale.linear()
.domain([0,6*scale])
.range([0,size])
function transform(d) {
return "translate("+X(d.x)+", "+Y(d.y)+")"
}
var circle /*fwd declaration*/
var zoom = d3.behavior.zoom()
.x(X).y(Y)
.on("zoom", function () {
circle.attr("transform", transform)
})
var svg = d3.select("body").append("svg")
.attr("width", size).attr("height", size)
.call(zoom)
.append("g")
circle = svg.selectAll("circle")
.data(circles)
.enter().append("circle")
.attr("r", 20)
/*the problem was this initial offset interfering with the
translation we were applying, resulting in very strange behavior*/
/* .attr("cx", function (d) {return d.x})
.attr("cy", function (d) {return d.y})*/
.attr("transform", transform)
The "scale" parameter should do nothing, but if you add in those commented lines, it affects the initial position and causes the non-intuitive effects.
The original problems were:
Initial scale appeared to be more zoomed than it should have been.
Zooming out very var produced a noticeable nonlinear asymptotic effect.
Zooming out then panning around, then zooming back in did not work at all like expected, with the diagram sliding under the mouse instead of staying pinned.
All of these are straightforward consequences of the initial position:
The initial distances appeared bigger because we applied their original positions plus the zoom translation.
The nonlinear asymptotic effect was the zoom translation distances going to zero asymptotically (as expected), but the initially applied distances not going to zero, giving the appearance of a nonzero zoom asymptote.
While zoomed out, D3 thinks it's zoomed out more than the user does (because of the extra distances between circles), which means when a pan is applied, the center of the image as D3 tracks it is moving differently than what the user expects, which causes the effect of the zoom center not being under the mouse.
You can play with these effects to understand them by uncommenting the initial position lines and applying the same zoom actions with different scale parameters. Commenting them causes the circles to initially be all at screen-space 0,0, so that only the zoom distance translation is applied, which is what we want.
Props to musically_ut's answer for suggesting the smaller world-space coordinate scale, which shouldn't have made any difference, but did, which helped me identify the problem.

Resources