I have a problem with a grid of rects in an SVG.
Here is my code over at jsFiddle: https://jsfiddle.net/swpcpvxL/
It is supposed to make a square by plotting one pixel at a time. I use a 1 by 1 SVG rect to plot a pixel.
var svg = document.getElementById("mysvg");
for (var y = 50; y <= 150; ++y) {
for (var x = 50; x <= 150; ++x) {
var r = Math.floor((x + y) / 250);
var g = Math.floor((Math.sin(x/10.0) + 1) * x);
var b = Math.floor((Math.sin(y/10.0) + 1) * x);
var color = "rgb(" + r + "," + g + "," + b + ")";
var k = document.createElementNS("http://www.w3.org/2000/svg", "rect");
k.setAttribute("x", x);
k.setAttribute("y", y);
k.setAttribute("width", "1");
k.setAttribute("height", "1");
k.setAttribute("style", "fill: " + color);
svg.appendChild(k);
}
}
My issue is that on Firefox the rect don't plot correctly. They show up the wrong color (washed out) and are actually translucent. I think the issue is that Firefox is doing anti-aliasing or something on the rects instead of just plotting the rect right on the pixel I want. I also tested on IE - it doesn't have this problem and the code works correctly. I don't have Chrome to test with.
I uploaded an image of what it looks like for me in Firefox here.
As you can see in the image, I can see the circles through the rects. This is not what I want at all!
How can I fix the Firefox problem? Or is there a better way to generate and plot a bitmap in a SVG like this? I've noticed that this method is a bit slow, so maybe there is a better approach.
Thanks!
I see the problem in my Firefox too. It does look like a blending problem of some sort. You can also see the image "flicker" if you move the window around or resize it.
A work around is to plot each rect as a 2x2 box, instead of a 1x1. So the only change is this:
k.setAttribute("width", "2");
k.setAttribute("height", "2");
You still step on the X and Y by one pixel at a time.
That way each rect overlaps the one on each side by 1 pixel. It will make your entire 100 square one pixel bigger to the right and bottom. You may want to shift the origin to compensate, if you use this method and care about that.
Related
I am trying to pan and zoom to a svg node using d3js. But I cannot get my head around the math here.
If I force the desired zoom level to be 1, then I seem to get it right.
Here's an example:
let svg = d3.select('svg'),
svgW = svg.node().getBoundingClientRect().width,
svgH = svg.node().getBoundingClientRect().height,
svgCentroid = {
x : svgW / 2,
y : svgH / 2
};
// zoom functionality has been applied to this one
let selector = d3.select('#container');
let elem = d3.select('[id="6"]'),
elemBounds = elem.node().getBBox(),
elemCentroid = {
x : elemBounds.x + (elemBounds.width / 2),
y : elemBounds.y + (elemBounds.height / 2)
};
let position = {
x : svgCentroid.x - elemCentroid.x,
y : svgCentroid.y - elemCentroid.y
};
selector.transition()
.duration(750)
.call(this.zoom.transform, d3.zoomIdentity
.translate(position.x, position.y)
// set scale to 1
.scale(1)
);
My first naive thought was "piece of cake". I will just multiply the calculated positions with desired zoom level. But, surprise surprise, that got me terribly wrong.
// failed miserably
selector.transition()
.duration(750)
.call(this.zoom.transform, d3.zoomIdentity
.translate(position.x * 5, position.y * 5)
.scale(5)
);
I've been trying to play around with this example:
https://bl.ocks.org/smithant/664d6cf86e53442d09687b154a9a411d
It pretty much sums up my intentions, but even though it's right there I don't fully understand it and thus it does not work properly with the rest of my code. I guess what confuses me most about this particular example are how the variables have their names declared.
I'd be grateful if someone could point me in the right direction here. How can I achieve this? What is the appropriate math to correctly zoom and pan within an SVG?
Thanks :)
I think that what you're looking for is:
function () {
var t = d3.transform(d3.select(this).attr("transform")),
x = t.translate[0],
y = t.translate[1];
var scale = 10;
svg.transition().duration(3000)
.call(zoom.translate([((x * -scale) + (svgWidth / 2)), ((y * -scale) + svgHeight / 2)])
.scale(scale).event);
}
Where this represents the element. Have a look here for a working example. In the example you'll be able to zoom to element after pressing on it. Also if panning and zooming an svg is all you need to do check out this library. It just works, no maths required :).
Lately I created a project involving drawing a lot of points on a canvas to plot a strange attractor. The details of this project aren't really relevant, but if you want to see it in action, go here: How can I check if an attractor is strange?
The problem I was encountering is the following: How can I draw a point on a canvas, whose color depends on the color, that was already there? In other words: How do I implement a color scale that depends on that number of times, a specific point has been colored?
I actually found a way, but I'm not convinced if it's the best. Here is how it works:
ctx.globalCompositeOperation = "lighter";
ctx.fillStyle = "rgb(50,5,1)";
ctx.fillRect(x,y,size,size);
It simply adds to the color that is already there. This can already look pretty good:
But there are also a lot of restrictions when using this method:
I can't get a colorchange from green to red for example
Using this method on a white background is impossible
I can't create a colorscale that includes more than to "fixed points", like for example red->green->blue
Maybe you know methods that work better than mine...
I think you need to track hits per pixel to implement a function that would allow you to change picture color, rather than just luminosity or redness. As suggested above, you should use a multi-dimensional array to track hits per pixel.
var canvasPixels = [];
for (var y = 0; y < 1000; y++) {
canvasPixels[y] = [];
for (var x = 0; x < 1000; x++) {
canvasPixels[y][x] = 0;
}
}
There are any number of things you can do if you apply the color math yourself. Here I'm using a color sine wave.
function getColor(hits) {
var frequency = 0.3;
var r = Math.round(Math.sin(frequency*hits + 0) * 127 + 128);
var g = Math.round(Math.sin(frequency*hits + 2) * 127 + 128);
var b = Math.round(Math.sin(frequency*hits + 4) * 127 + 128);
return "rgb(" + r + "," + g + "," + b + ")";
}
Then, you just use this function when drawing to cycle through the rainbow.
canvasPixels[y][x]++;
ctx.fillStyle = getColor(canvasPixels[y][x]);
ctx.fillRect(x,y,size,size);
This relates to this bug:
https://code.google.com/p/svg-edit/wiki/BrowserBugs#getBBox_on_paths_with_curves_includes_control_points
Given an arc (center, radius, startAngle, endAngle), I am able to calculate points along the path then their bounding box, however, in safari and chrome there is a bug that includes control points of the arc in the bounding box. Since gradient fills are applied to a shape's bounding box, this results in the gradient covering the shape slightly different depending on whether the browser has this bug or not.
My question is: without using the actual API (getBBox()) how can I mathematically calculate the extra control points of an arc in a path to adjust for the safari/chrome bug given these parameters (center, radius, startAngle, endAngle)?
This doesnt have to work with bezier curves or even ellipses, just a simple circular arc.
Thank You!
I wrote a getBBox shim for webkit browsers. Demo here
//webkit(safari, chrome) path element getBBox bug
if(navigator.userAgent.indexOf('AppleWebKit')>0){
SVGPathElement.prototype.getBBox = function (precision){
var path = this;
var total = path.getTotalLength();
if(total <= 0){
return {x:0,y:0,width:0,height:0};
}
var segLen = precision || 1;
var len = 0, pt, xarr = [], yarr = [];
while(len < total){
pt = path.getPointAtLength(len);
xarr.push(pt.x);
yarr.push(pt.y);
len += segLen;
}
pt = path.getPointAtLength(total);
xarr.push(pt.x);
yarr.push(pt.y);
var b = {};
b.x = Math.min.apply(0,xarr);
b.y = Math.min.apply(0,yarr);
b.width = Math.max.apply(0,xarr) - b.x;
b.height = Math.max.apply(0,yarr) - b.y;
return b;
};
}
For a hint on how you might implement a shim for getBBox(), take a look at what Snap.svg is doing here: https://github.com/adobe-webplatform/Snap.svg/blob/master/src/path.js#L414
In fact, for a <path> DOM element, you can get a correct BBox by calling:
Snap.path.getBBox(pathElement.getAttribute('d'))
I am creating a new "whack-a-mole" style game where the children have to hit the correct numbers in accordance to the question. So far it is going really well, I have a timer, count the right and wrong answers and when the game is started I have a number of divs called "characters" that appear in the container randomly at set times.
The problem I am having is that because it is completely random, sometimes the "characters" appear overlapped with one another. Is there a way to organize them so that they appear in set places in the container and don't overlap when they appear.
Here I have the code that maps the divs to the container..
function randomFromTo(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
function scramble() {
var children = $('#container').children();
var randomId = randomFromTo(1, children.length);
moveRandom('char' + randomId);
}
function moveRandom(id) {
var cPos = $('#container').offset();
var cHeight = $('#container').height();
var cWidth = $('#container').width();
var pad = parseInt($('#container').css('padding-top').replace('px', ''));
var bHeight = $('#' + id).height();
var bWidth = $('#' + id).width();
maxY = cPos.top + cHeight - bHeight - pad;
maxX = cPos.left + cWidth - bWidth - pad;
minY = cPos.top + pad;
minX = cPos.left + pad;
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX);
$('#' + id).css({
top: newY,
left: newX
}).fadeIn(100, function () {
setTimeout(function () {
$('#' + id).fadeOut(100);
window.cont++;
}, 1000);
});
I have a fiddle if it helps.. http://jsfiddle.net/pUwKb/8/
As #aug suggests, you should know where you cannot place things at draw-time, and only place them at valid positions. The easiest way to do this is to keep currently-occupied positions handy to check them against proposed locations.
I suggest something like
// locations of current divs; elements like {x: 10, y: 40}
var boxes = [];
// p point; b box top-left corner; w and h width and height
function inside(p, w, h, b) {
return (p.x >= b.x) && (p.y >= b.y) && (p.x < b.x + w) && (p.y < b.y + h);
}
// a and b box top-left corners; w and h width and height; m is margin
function overlaps(a, b, w, h, m) {
var corners = [a, {x:a.x+w, y:a.y}, {x:a.x, y:a.y+h}, {x:a.x+w, y:a.y+h}];
var bWithMargins = {x:b.x-m, y:b.y-m};
for (var i=0; i<corners.length; i++) {
if (inside(corners[i], bWithMargins, w+2*m, h+2*m) return true;
}
return false;
}
// when placing a new piece
var box;
while (box === undefined) {
box = createRandomPosition(); // returns something like {x: 15, y: 92}
for (var i=0; i<boxes.length; i++) {
if (overlaps(box, boxes[i], boxwidth, boxheight, margin)) {
box = undefined;
break;
}
}
}
boxes.push(box);
Warning: untested code, beware the typos.
The basic idea you will have to implement is that when a random coordinate is chosen, theoretically you SHOULD know the boundaries of what is not permissible and your program should know not to choose those places (whether you find an algorithm or way of simply disregarding those ranges or your program constantly checks to make sure that the number chosen isn't within the boundary is up to you. the latter is easier to implement but is a bad way of going about it simply because you are entirely relying on chance).
Let's say for example coordinate 50, 70 is selected. If the picture is 50x50 in size, the range of what is allowed would exclude not only the dimensions of the picture, but also 50px in all directions of the picture so that no overlap may occur.
Hope this helps. If I have time, I might try to code an example but I hope this answers the conceptual aspect of the question if that is what you were having trouble with.
Oh and btw forgot to say really great job on this program. It looks awesome :)
You can approach this problem in at least two ways (these two are popped up in my head).
How about to create a 2 dimensional grid segmentation based on the number of questions, the sizes of the question panel and an array holding the position of each question coordinates and then on each time frame to position randomly these panels on one of the allowed coordinates.
Note: read this article for further information: http://eloquentjavascript.net/chapter8.html
The second approach follow the same principle, but this time to check if the panel overlap the existing panel before you place it on the canvas.
var _grids;
var GRID_SIZE = 20 //a constant holding the panel size;
function createGrids() {
_grids = new Array();
for (var i = 0; i< stage.stageWidth / GRID_SIZE; i++) {
_grids[i] = new Array();
for (var j = 0; j< stage.stageHeight / GRID_SIZE; j++) {
_grids[i][j] = new Array();
}
}
}
Then on a separate function to create the collision check. I've created a gist for collision check in Actionscript, but you can use the same principle in Javascript too. I've created this gist for inspirational purposes.
Just use a random number which is based on the width of your board and then modulo with the height...
You get a cell which is where you can put the mole.
For the positions the x and y should never change as you have 9 spots lets say where the mole could pop up.
x x x
x x x
x x x
Each cell would be sized based on % rather then pixels and would allow re sizing the screen
1%3 = 1 (x)
3%3 = 0 (y)
Then no overlap is possible.
Once the mole is positioned it can be show or hidden or moved etc based on some extended logic if required.
If want to keep things your way and you just need a quick re-position algorithm... just set the NE to the SW if the X + width >= x of the character you want to check by setting the x = y+height of the item which overlaps. You could also enforce that logic in the drawing routine by caching the last x and ensuring the random number was not < last + width of the item.
newY = randomFromTo(minY, maxY);
newX = randomFromTo(minX, maxX); if(newX > lastX + characterWidth){ /*needful*/}
There could still however be overlap...
If you wanted to totally eliminate it you would need to keep track of state such as where each x was and then iterate that list to find a new position or position them first and then all them to move about randomly without intersecting which would would be able to control with just padding from that point.
Overall I think it would be easier to just keep X starting at 0 and then and then increment until you are at a X + character width > greater then the width of the board. Then just increase Y by character height and Set X = 0 or character width or some other offset.
newX = 0; newX += characterWidth; if(newX + chracterWidth > boardWidth) newX=0; newY+= characterHeight;
That results in no overlap and having nothing to iterate or keep track of additional to what you do now, the only downside is the pattern of the displayed characters being 'checker board style' or right next to each other (with possible random spacing in between horizontal and vertical placement e.g. you could adjust the padding randomly if you wanted too)
It's the whole random thing in the first place that adds the complexity.
AND I updated your fiddle to prove I eliminated the random and stopped the overlap :)
http://jsfiddle.net/pUwKb/51/
Over the last two days I've effectively figured out how NOT to rotate Raphael Elements.
Basically I am trying to implement a multiple pivot points on element to rotate it by mouse.
When a user enters rotation mode 5 pivots are created. One for each corner of the bounding box and one in the center of the box.
When the mouse is down and moving it is simple enough to rotate around the pivot using Raphael elements.rotate(degrees, x, y) and calculating the degrees based on the mouse positions and atan2 to the pivot point.
The problem arises after I've rotated the element, bbox, and the other pivots. There x,y position in the same only there viewport is different.
In an SVG enabled browser I can create new pivot points based on matrixTransformation and getCTM. However after creating the first set of new pivots, every rotation after the pivots get further away from the transformed bbox due to rounding errors.
The above is not even an option in IE since in is VML based and cannot account for transformation.
Is the only effective way to implement
element rotation is by using rotate
absolute or rotating around the center
of the bounding box?
Is it possible at all the create multi
pivot points for an object and update
them after mouseup to remain in the
corners and center of the transformed
bbox?
UPDATE:
I've attempted to use jQuery offset to find the pivot after it's been rotated, and to use that offset location as the pivot point.
Demo site ...
http://weather.speedfetishperformance.com/dev/raphael/rotation.html
The best cross-browser way I can think of to do what you want is to implement the rotation yourself rather than let SVG do it. Rotating x,y coordinates is fairly simple and I've been using this (tcl) code whenever I need to do 2D rotation: Canvas Rotation.
The upside to this is you have maximum control of the rotation since you're doing it manually. This solves the problems you're having trying to guess the final coordinates after rotation. Also, this should be cross browser compatible.
The downside is you have to use paths. So no rects (though it should be easy to convert them to paths) or ellipses (a little bit harder to convert to path but doable). Also, since you're doing it manually, it should be slower than letting SVG do it for you.
Here's a partial implementation of that Tcl code in javascript:
first we need a regexp to tokenize SVG paths:
var svg_path_regexp = (function(){
var number = '-?[0-9.]+';
var comma = '\s*[, \t]\s*';
var space = '\s+';
var xy = number + comma + number;
var standard_paths = '[mlcsqt]';
var horiz_vert = '[hv]\s*' + number;
var arc = 'a\s*' + xy + space + number + space + xy + space + xy;
var OR = '\s*|';
return new RegExp(
standard_paths +OR+
xy +OR+
horiz_vert +OR+
arc,
'ig'
);
})();
now we can implement the rotate function:
function rotate_SVG_path (path, Ox, Oy, angle) {
angle = angle * Math.atan(1) * 4 / 180.0; // degrees to radians
var tokens = path.match(svg_path_regexp);
for (var i=0; i<tokens.length; i++) {
var token = tokens[i].replace(/^\s+|\s+$/g,''); // trim string
if (token.match(/\d/)) { // assume it's a coordinate
var xy = token.split(/[, \t]+/);
var x = parseFloat(xy[0]);
var y = parseFloat(xy[1]);
x = x - Ox; // Shift to origin
y = y - Oy;
var xx = x * Math.cos(angle) - y * Math.sin(angle); // Rotate
var yy = x * Math.sin(angle) + y * Math.cos(angle);
x = xx + Ox; // Shift back
y = yy + Oy;
token = x + ',' + y;
}
else if (token.match(/^[hv]/)) {
// handle horizontal/vertical line here
}
else if (token.match(/^a/)) {
// handle arcs here
}
tokens[i] = token;
}
return tokens.join('');
}
The above rotate function implements everything except horizontal/vertical lines (you need to keep track of previous xy value) and arcs. Neither should be too hard to implement.