Creating a HTML5 infinite canvas - javascript

I'm trying to learn how to build an interactive canvas from scratch, but I'm having trouble trying to draw things outside the canvas' viewport (things that exceed canvas.width and canvas.height). The goal is to have something like an infinite canvas where you can scroll and zoom and put things anywhere I want.
I figured out how to properly calculate the right insertion point when we zoomed out, the algorithm works like this:
see if the component should be added off the limits of the canvas;
if so, transform the offset (x, y) adding the distance between the point and the edge of the canvas.
I noticed that the event.pageX and event.pageY values are always given to me based on the width and height of the canvas, so if I'm zoomed out these values are, then, smaller than it should be (since I'm viewing more pixels). The transform algorithm work as follows in JS:
// pageX is 430, pageY is 480
// canvas has width=600 height=600
// scale is 0.6, meaning the canvas actually has size 360x360
var currentSize = canvas.width * scale; // 360
pageX = canvas.width + (pageX - currentSize);
pageY = canvas.width + (pageY - currentSize);
Drawing on paper this logic seem to work, but the problem is I (apparently) can't draw outside canvas limits, so I'm unable to see the result. Questions are:
Is this logic correct?
Is there a way to achieve my goal? (pointing right literature will be very appreciated)
Is canvas the right tool to the job or I should use something else?
The complete example I'm using to learn can be found on this fiddle.
UPDATE
I had another idea to solve the problem: instead of drawing things outside the canvas, I simply translate my points to fit inside the canvas' limits proportionally and then apply scale to zoom in/out. Something like this:
// canvas is 500x500
var points = [
{text: 'Foo', x: 10, y: 10},
{text: 'Bar', x: 854, y: 552}, // does not fit inside
{text: 'Baz', x: 352, y: 440}
];
// The canvas can't show all these points, the ideal
// would be having a canvas of at least size 900x600,
// so I can use a rule of three to convert all points
// from this imaginary canvas to fit inside my 500x500
// in 900px, x=10
// in 500px, x=?
// hence, the formulas are `newX=x * 500 / 900` and `newY = y * 500 / 600`
var converted_points = [
{text: 'Foo', x: 5.55, y: 8.33},
{text: 'Bar', x: 474.44, y: 460},
{text: 'Baz', x: 195.55, y: 366.66}
];
After that I suppose I would just need to scale/transform the canvas to do zooming. Is that logic ok?

You can use the library called TiledCanvas
It provides interfaces to zoom and move.
And draw in an infinite space using all the canvas apis.
It does require that you tell where you are drawing.
https://github.com/Squarific/TiledCanvas

Related

How to calculate how many rectangles fit in a polygon?

I'm creating an algorithm using JavaScript + Canvas, the user can draw a polygon, after it I want to calculate how many rectangles (suppose I know the dimensions) fit inside this polygon?
For example, I have 3 examples of a yellow polygon (rectangle, triangle, and a complex), I want to know how many rectangles fit inside it.
If rectangles can be cut
The area of a rectangle is width * height.
The area of any polygon with straight edges can be found from the set of points that define the vertices using the function below. Half the sum of the cross products of vertices defining each edge.
const polyArea = points => {
var i = 0, area = 0, len = points.length;
while (i < len) {
const p1 = points[i++];
const p2 = points[i % len];
area += p1.x * p2.y - p1.y * p2.x;
}
return Math.abs(0.5 * area);
}
// example
const triangleArea = polyArea([{x:100, y:0}, {x: 200, y: 200}, {x: 0, y: 200}]);
The max number of rectangles that can fit in a polygon is the polygonArea / rectangleArea.
If rectangles can not be cut
However the actual number of rectangles will be some value from 0 to the number calculated above.
There is no simple solution to the problem apart from the brute force approach of placing rectangles in different patterns to see which pattern is the best fit. For each polygon there is an infinite number of patterns that can be used, thus any solution will be just the best of many guess if you want id done before lunch.

Constraining map panning with zoom.translateExtent in D3 v4

I'm trying to display a map of a single state, with zooming and panning constrained to the boundaries of the state. It's mostly working, except for the panning constraint when the state path is scaled to fit a smaller container. I think this comes down to me not understanding what arguments to use for zoom.translateExtent (although I'm very new to this, so it could be something else).
Live example on bl.ocks.org, with links to prior art.
One notable thing is that I'm using a null projection for d3.geoPath, because I used ogr2ogr to generate a shapefile in projected coordinates for each state. That's why I used a zoom transform to fit the map to its container.
#McGiogen's solution is almost correct but misses that MIN needs to vary depending on the zoom scale factor transform.k.
I drew a diagram to see how I needed to constrain my svg to always be contained inside the zoomed view (depicted in my drawing as the LARGER of the boxes, only a portion of which is visible to the user):
(since the constraint x+kw >= w is equivalent to x >= (1-k)w, with a similar argument for y)
thus assuming your svg container size [w, h]:
function zoomed() {
var t = d3.event.transform;
t.x = d3.min([t.x, 0]);
t.y = d3.min([t.y, 0]);
t.x = d3.max([t.x, (1-t.k) * w]);
t.y = d3.max([t.y, (1-t.k) * h]);
svg.attr("transform", t);
}
I'm facing the same problem today and I've done some tests.
I've noticed that it's the same weird behaviour happening when you have a translateExtent box smaller than the content's elements.
In your (and mine) code the same behaviour is triggered by zooming out: it doesn't matter if you have the translateExtent box correctly set with no zoom, if you zoom out the box is reduced at higher rate than the elements and at some point you will have translateExtent box smaller than the content (and the weird behaviour).
I temporary solved this as said here
D3 pan+ zoom constraints
var MIN = {x: 0, y: -500}, //top-left corner
MAX = {x: 2000, y: 500}; //bottom-right corner
function zoomed() {
var transform = d3.event.transform;
// limiting tranformation by MIN and MAX bounds
transform.x = d3.max([transform.x, MIN.x]);
transform.y = d3.max([transform.y, MIN.y]);
transform.x = d3.min([transform.x, MAX.x]);
transform.y = d3.min([transform.y, MAX.y]);
container.attr("transform", transform);
}
I'm still a d3 newbie but I think that this is a bug in translateExtent code.

Best way to use multiple canvases in single page

I have a dynamic graph with real time array values which using canvas to plot graph which is working fine, but I want over 50 unique graphs in single page with 50 different array of values.
What is the best way to achieve this? I have goggled it got to know that using multiple canvas application performance may slow, but my application is for desktops not for mobiles.
-- Edit--
I did some thing like this to get my requirement..
http://jsfiddle.net/atluriajith/v4Rhv/
graphs are plotting properly upto 100, after that the speed of the graphs are getting slow. Is this the right way what i did?
-- Edit--
You can use a single canvas that is big enough to hold the graphs.
Then you can use translate and clip to draw the graphs into isolated areas on that canvas (aka. "virtual canvases"). Depending on how you intend to draw the graphs even this may not be necessary.
This only needs a single clear before you redraw the graphs.
In this case you would probably organize it into 5 x 10 cells.
For example (disclaimer: not tested, meant for example):
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
cols = 5,
rows = 10,
cellWidth = 200,
cellHeight = 100;
canvas.width = cols * cellWidth;
canvas.height = rows * cellHeight;
document.body.appendChild(canvas);
...
/// draw a graph:
var x = cellX * cellWidth, /// cell indexes to position
y = cellY * cellHeight;
ctx.save();
ctx.rect(x, y, cellWidth, cellHeight);
ctx.clip();
/// draw graph here
ctx.restore();
If you can contain the graph within x, y, and cellWidth/cellHeight the clipping is not necessary.
Before you continue I would think it worthwhile to verify that you have performance issues.
"Premature optimization is the root of all evil"
One fairly straight forward solution is simply to reduce the number of canvasses you're rendering. Make your UI such that you never render more canvasses than you can reasonable support with a given performance. No one needs to see 50 graphs at once, that is simply too much information.

Drawing layers with jcanvas: performance optimization

)
I have a small web-application which uses jquery and jcanvas (http://calebevans.me/projects/jcanvas/) to print some shapes onto a canvas.
It's basically a map. The user can zoom into it and drag it around and whenever he does so everything has to be drawn again.
As you may see in the code below I remove and recreate layers pretty often (whenever the user drags the map, zooms or resizes his window). I need the layers to handle hover- and click-events. My question is whether there is a big performance impact of this way to handle events in comparison to other solutions. If this is the case, how could I optimize my performance?
var posX = 0, posY = 0;
var zoom = 100;
var points = []; //array of up to 1000 points retrieved by ajax
function draw(){
$("canvas").removeLayers();
$("canvas").clearCanvas();
var xp, yp, ra;
var name;
$.each(points, function(index) {
xp = (this["x"]-posX)/zoom;
yp = (this["y"]-posY)/zoom;
ra = 1000/zoom;
$("#map").drawArc({
layer:true,
fillStyle: "black",
x: xp,
y: yp,
radius: ra,
mouseover: function(layer) {
$(this).animateLayer(layer, {
fillStyle: "#c33",
scale: 1.0
},200);
},
mouseout: function(layer) {
$(this).animateLayer(layer, {
fillStyle: "black",
scale: 1.0
},200);
},
mouseup: function(layer){
context(index,layer.x,layer.y);
}
});
});
}
Thank you for your time :-)
Optimization is in general a case-to-case thing but I'll try to give some general points for this case:
Keep operations to a minimum
Creating and removing "layers" (canvas elements) are costly operations - try to avoid if possible.
Rather move content around using drawImage() on itself or use clearRect() if you need to clear the whole canvas.
The same with a little more details:
You can for example use drawImage() to move the content to one of the sides, if we wanted to move everything to the left by 10 pixels we could do:
context.drawImage(canvas, 10, 0, canvas.width - 10, canvas.height,
0, 0, canvas.width - 10, canvas.height);
This will clip only the part we want to move/scroll and then redraw it in the new position.
Finally draw in the 10px gap at right with new graphics (you would probably want to cache the width and height but frankly, in the modern JavaScript engines this does not matter so much anymore as it did in the past). Test your points to see what points actually needs to be drawn to avoid drawing all of them again which otherwise renders this step pointless.
If you want to clear the canvas, rather than removing the canvas element and create a new canvas use the method clearRect() or use fillRect() if you want a pattern, color etc. Removing and creating elements are a costly operation from a optimization perspective especially if they affect the browser's flow (if it need to reflow the content). It triggers a bunch of operations from layout, css parsing, repaint etc. Reuse if you can.

How can I simulate z-index in canvas

I have asked a question before: How can I control z-index of canvas objects? and we reached to a solution that may not be a good one for complicated situations.
I found that canvas doesn't have a z-index system, but a simple ordered drawing one. Now there is a new question: how can I simulate z-index system to make this problem fixed in complicated situations?
The good answer can solve a big problem.
It's not that canvas doesn't have a z-index, it's that canvas doesn't keep objects drawn contrary to the HTML page. It just draws on the pixel matrix.
There are basically two types of drawing models :
object ones (usually vector) : objects are kept and managed by the engine. They can usually be removed or changed. They have a z-index
bitmap ones : there are no objects. You just change a pixel matrix
The Canvas model is a bitmap one. To have objects drawn over other ones, you must draw them after. This means you must manage what you draw.
The canvas model is very fast, but if you want a drawing system managing your objects, maybe you need SVG instead.
If you want to use a canvas, then the best is to keep what you draw as objects.
Here's an example I just made : I keep a square list and every second I randomize their zindex and redraw them :
var c = document.getElementById('c').getContext('2d');
function Square(x, y, s, color) {
this.x = x; this.y = y; this.s = s; this.color = color;
this.zindex=0;
}
Square.prototype.draw = function(c) {
c.fillStyle = this.color;
c.fillRect(this.x, this.y, this.s, this.s);
}
var squares = [
new Square(10, 10, 50, 'blue'), new Square(40, 10, 40, 'red'), new Square(30, 50, 30, 'green'),
new Square(60, 30, 40, '#111'), new Square(0, 30, 20, '#444'), new Square(70, 00, 40, '#999')
];
function draw() {
c.fillStyle = "white";
c.fillRect(0, 0, 1000, 500);
for (var i=0; i<squares.length; i++) squares[i].draw(c);
}
setInterval(function(){
// give all squares a random z-index
squares.forEach(function(v){v.zindex=Math.random()});
// sort the list accordingly to zindex
squares.sort(function(a,b){return a.zindex-b.zindex});
draw();
}, 1000);
Demonstration
The idea is that the square array is sorted accordingly to zindex. This could be easily extended to other types of objects.
As dystroy has said, z-index is, at its simplest, just an index to tell you in what order to draw things on the canvas, so that they overlap properly.
If you mean to do more than this, say to replicate the existing workings of a browser, then you would have more work to do. The order in which objects are drawn in a browser is a complicated calculation that is driven by:
The DOM tree
Elements' position attributes
Elements' z-index attributes
The canonical source to this is the Elaborate description of Stacking Contexts, part of the CSS specification.

Categories

Resources