In my SVG based web application, a user can select a large number of shapes (even 800 or more) & move them about on the screen, which as one can imagine severely impacts the framerate. After reading the merits of requestAnimationFrame, I have been working since yesterday trying to incorporate it into my mousemove function, with the hope that by using a combination of throttling & requestAnimationFrame, I can get to a smooth framerate.
Here is the jsFiddle in which I am moving 600 svg shapes on mousemove. Ignoring throttling for now, how can I incorporate requestAnimationFrame into the mousemove function.
http://jsfiddle.net/rehankhalid/5t5pX/
HTML CODE
<svg width="1200" height="800">
<symbol>
<g id="symbol1">
<path fill="#3ab6d1" d="M27.7,1.1C16-2,3.9,5.1,0.8,16.9s4,23.8,15.7,26.9c11.7,3.1,23.8-4,26.9-15.7C46.6,16.3,39.6,4.2,27.7,1.1z M38.3,26.9C36,35.7,26.9,41,18,38.7C9.1,36.4,3.8,27.3,6.1,18.5C8.4,9.6,17.5,4.3,26.4,6.6C35.3,9,40.6,18,38.3,26.9z"/>
<g fill="#d5e1db">
<path d="m25.8,.8c2.5,.4 4.8,1.2 7,2.5l-2.9,4.6c-1.5-.8-3.1-1.4-4.8-1.7l.7-5.4z"/>
<path d="m13.9,2.2l1.7,5.1c-1.5,.7-3,1.5-4.3,2.7l-3.8-3.9c.9-.8 1.9-1.5 2.9-2.2 1.2-.7 2.3-1.3 3.5-1.7z"/>
</g>
<path fill="#196275" d="m26.4,38.5l1.8,5.2c-2.5,.7-4.9,.9-7.4,.8l.6-5.4c1.6,0 3.3-.2 5-.6z"/>
<path fill="#42aec2" d="M6.1,18.4C8.4,9.5,17.5,4.2,26.4,6.5S40.7,18,38.4,26.8C36,35.7,26.9,41,18,38.7C9.1,36.3,3.7,27.2,6.1,18.4 z"/>
<path fill="#d2dfdd" d="m28.7,25.9l.5-4.9 9.5-1.3c0,.3 .1,.6 .2,.8 .4,2.9 0,5.8-1,8.3l-9.2-2.9z"/>
<path fill="#1b6273" d="m19.7,29.3h4.9l2.1,9.4c-.3,.1-.6,.2-.8,.2-2.9,.6-5.7,.5-8.4-.3l2.2-9.3z"/>
<path fill="#368098" d="m15.9,25.8l3.6,3.4-4.8,8.3c-.3-.1-.5-.3-.8-.4-2.6-1.5-4.6-3.5-6-5.9l8-5.4z"/>
<path fill="#d2dfdd" d="m18.6,16.5l-3,3.9-8.7-4c.1-.3 .2-.5 .3-.8 1.2-2.7 3.1-4.9 5.3-6.5l6.1,7.4z"/>
<path fill="#bde2e9" d="m26.4,32.1l-8.4-.5-4.9-6.8 2.3-8 7.7-3.2 7.3,4 1.5,8.2-5.5,6.3z"/>
</g>
</symbol>
<g id="default">
<g id="my-element">
</g>
</g>
<g id="draggroup">
</g>
</svg>
Javascript
document.addEventListener('mousedown', mousedown, false);
var element = document.getElementById('my-element');
var defaultg = document.getElementById('default');
var mainsvg = document.getElementsByTagName('svg')[0];
var draggroup = document.getElementById('draggroup');
var svgns = "http://www.w3.org/2000/svg";
var x = 0, y = 0;
var scale = '0.25';
for (var i = 0; i < 600; i++) {
var useelement = document.createElementNS(svgns, "use");
useelement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#symbol1');
useelement.setAttribute('transform', 'translate(' + x + ',' + y + ') scale(' + scale + ')');
x += 12;
if (x === 240) {
x = 0;
y += 12;
}
element.appendChild(useelement);
}
var bbox = element.getBBox();
var selectionRect = document.createElementNS(svgns, "rect");
selectionRect.setAttribute('id', 'selectionrect');
selectionRect.setAttribute('x', bbox.x);
selectionRect.setAttribute('y', bbox.y);
selectionRect.setAttribute('width', bbox.width);
selectionRect.setAttribute('height', bbox.height);
selectionRect.setAttribute('fill', 'grey');
selectionRect.setAttribute('opacity', '0.2');
draggroup.appendChild(selectionRect);
var dx = 0, dy = 0, mx = 0, my = 0;
var rectdx = 0, rectdy = 0;
var elementdx = 0, elementdy = 0;
function getSvgCordinates(event) {
var m = mainsvg.getScreenCTM();
var p = mainsvg.createSVGPoint();
var x, y;
x = event.pageX;
y = event.pageY;
p.x = x;
p.y = y;
p = p.matrixTransform(m.inverse());
x = p.x;
y = p.y;
x = parseFloat(x.toFixed(3));
y = parseFloat(y.toFixed(3));
return {x: x, y: y};
}
function mousedown(event) {
if (event.target.id === 'selectionrect') {
var svgXY = getSvgCordinates(event);
mx = svgXY.x; // mouse down x
my = svgXY.y;// mouse down y
draggroup.appendChild(element);
document.addEventListener('mousemove', mousemove, false);
document.addEventListener('mouseup', mouseup, false);
}
}
function mouseup() {
document.removeEventListener('mousemove', mousemove, false);
document.removeEventListener('mouseup', mouseup, false);
draggroup.setAttribute('transform', 'translate(0,0)');
rectdx += dx;
rectdy += dy;
elementdx += dx;
elementdy += dy;
selectionRect.setAttribute('transform', 'translate(' + rectdx + ',' + rectdy + ')');
element.setAttribute('transform', 'translate(' + elementdx + ',' + elementdy + ')');
defaultg.appendChild(element);
dx = 0;
dy = 0;
}
function mousemove(event) {
var svgXY = getSvgCordinates(event);
dx = svgXY.x - mx;
dy = svgXY.y - my;
draggroup.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
}
Basically, you'd need to move the draggroup.setAttribute() calls to another function. Since you only need to animate when the mouse is down, add another variable to indicate whether you're dragging or not, and only call requestAnimationFrame when that's the case.
var isDragging = false;
function update() { // New function
if (isDragging) {
requestAnimationFrame(update); // Call self again, if still dragging
}
draggroup.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
}
function mousedown(event) {
if (event.target.id === 'selectionrect') {
isDragging = true;
requestAnimationFrame(update); // Start animation loop
// existing logic here...
}
}
function mouseup() {
isDragging = false;
// existing logic here...
}
You're already storing the updated location for the group (dx, dy) in a separate variable, so remove the draggroup.setAttribute() call from mousemove() and add the above modifications, and you should be good!
Related
Hi I'm facing a problem with canvas.
I try to make a circle that can be reshape like this.
In the demo the circle can be reshape
the problem is to drag and drop the circle point to reshape it.
I know how to drag and drop point in the javascript canvas but how to reshape the cirle line to follow the point.
const DEBUG = true;
const WIDTH = window.innerWidth;
const HEIGHT = window.innerHeight;
const MIN_DIMENSION = WIDTH < HEIGHT ? WIDTH : HEIGHT;
const DEFAULT_RADIUS = MIN_DIMENSION * 0.45;
let canvas, ctx;
let cos = Math.cos;
let sin = Math.sin;
let pi = Math.PI;
let pi2 = pi * 2;
class Point {
constructor(x,y) {
this.x = x;
this.y = y;
}
}
function block(c, cb) {
c.save();
c.beginPath();
cb(c);
c.closePath();
c.restore();
}
function circle(c,r) {
c.arc(0, 0, r, 0, pi2);
}
function debugPoints(c, points) {
points.forEach((p,i) => {
if(i % 2 === 0) {
c.fillStyle = 'red';
} else {
c.fillStyle = 'black';
}
c.beginPath();
c.arc(p.x, p.y, 2, 0, pi2);
c.fill();
c.closePath();
})
}
function bezierCirclePoints(r, n) {
let a = pi2/(2*n);
let R = r/cos(a);
let points = new Array(2 * n);
console.log('n:', n);
console.log('a:', a);
console.log('r:', r);
console.log('R:', R);
// calculate even bezier points
for(let i = 0; i < n; i++) {
let i2 = 2*i;
let x = r * sin(i2 * a);
let y = -r * cos(i2 * a);
points[i2] = new Point(x, y);
}
// calculate odd bezier points
for(let i = 0; i < n; i++) {
let i2 = 2*i + 1;
let x = R * sin(i2 * a);
let y = -R * cos(i2 * a);
points[i2] = new Point(x, y);
}
points.push(points[0]);
return points;
}
function bezierCircle(c, r = DEFAULT_RADIUS, n = 7) {
let points = bezierCirclePoints(r,n);
c.translate(WIDTH * 0.5,HEIGHT * 0.5);
if(DEBUG) {
debugPoints(c, points);
}
c.fillStyle = 'red';
c.strokeStyle = 'red';
// draw circle
c.beginPath();
let p = points[0];
c.moveTo(p.x, p.y);
for(let i = 1; i < points.length; i+=2){
let p1 = points[i];
let i2 = i + 1;
if(i2 >= points.length) {
i2 = 0;
}
let p2 = points[i2];
c.quadraticCurveTo(p1.x, p1.y, p2.x, p2.y);
}
c.stroke();
c.closePath();
}
function redCircle(c) {
c.fillStyle = 'red';
c.translate(200,200);
circle(c, 100);
c.fill();
}
canvas = document.getElementById('circle');
canvas.width = WIDTH;
canvas.height = HEIGHT;
ctx = canvas.getContext('2d');
block(ctx, bezierCircle)
<canvas id="circle"></canvas>
As you already realized a circle can be composed out of four bézier curves. I'm going to use a cubic instead of a quadratic though since it offers two control points.
Let's start by looking at the following illustration:
As we can see the red curve consists of start point A, end point B and two control points c1 & c2 respectively.
So if we want to have a circle at x, y with a radius of r we can say:
Ax = x ; Ay = y - r
Bx = x + r ; By = y
c1x = x + r / 2 ; c1y = y - r
c2x = x + r ; c2y = y - r / 2
Of course the missing three curves can be constructed in the same way.
What we can also see from the above illustration is that the start point for the red segment is also the end point for the orange segment. Likewise the orange segment's control point c8 is connected to the start point of the red segment.
So if we're about to move point A we need to move the orange segment's end point, the red segment's start point AND the two control points c8 and c1.
To do this I'd write a general Arc class which consists of the start point, the end point, the two control points and additionally to which arc the start point is connected to. Then it goes a little something like this:
if someone clicks on point A, B, C or D store the current mouse position
store the position of the arc's control point as well as the connected arc's control point
if the mouse is moved, move the start point, it's control point and the connected arc's end point and it's control point relative to the mouse movement
repaint the circle
Here's an example:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Arc {
constructor(pointA, pointB, controlPointA, controlPointB) {
this.pointA = pointA;
this.pointB = pointB;
this.controlPointA = controlPointA;
this.controlPointB = controlPointB;
this.controlPointOldA = null;
this.controlPointOldB = null;
}
update(x, y, x2, y2) {
this.pointA.x = x;
this.pointA.y = y;
this.connectedArc.pointB.x = x;
this.connectedArc.pointB.y = y;
this.controlPointA.x = this.controlPointOldA.x + x2;
this.controlPointA.y = this.controlPointOldA.y + y2;
this.connectedArc.controlPointB.x = this.controlPointOldB.x + x2;
this.connectedArc.controlPointB.y = this.controlPointOldB.y + y2;
}
connect(connectedArc) {
this.connectedArc = connectedArc;
}
saveControlPoints() {
this.controlPointOldA = new Point(this.controlPointA.x, this.controlPointA.y);
this.controlPointOldB = new Point(this.connectedArc.controlPointB.x, this.connectedArc.controlPointB.y);
}
}
class Circle {
constructor(x, y, radius) {
this.arcA = new Arc(new Point(x, y - radius), new Point(x + radius, y), new Point(x + radius / 2, y - radius), new Point(x + radius, y - radius / 2));
this.arcB = new Arc(new Point(x + radius, y), new Point(x, y + radius), new Point(x + radius, y + radius / 2), new Point(x + radius / 2, y + radius));
this.arcC = new Arc(new Point(x, y + radius), new Point(x - radius, y), new Point(x - radius / 2, y + radius), new Point(x - radius, y + radius / 2));
this.arcD = new Arc(new Point(x - radius, y), new Point(x, y - radius), new Point(x - radius, y - radius / 2), new Point(x - radius / 2, y - radius));
this.arcA.connect(this.arcD);
this.arcB.connect(this.arcA);
this.arcC.connect(this.arcB);
this.arcD.connect(this.arcC);
}
}
var circle = new Circle(150, 150, 75);
var mouseX, mouseY, selectedArc;
var width = 5;
var height = 5;
var dragging = false;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var arcs = [circle.arcA, circle.arcB, circle.arcC, circle.arcD];
var points = document.getElementsByClassName("point");
var arc;
for (var a = 0; a < points.length; a++) {
arc = arcs[a];
points[a].setAttribute('data-linkedID', a);
points[a].style.left = (arc.pointA.x - width) + "px";
points[a].style.top = (arc.pointA.y - height) + "px";
points[a].addEventListener("mousedown", dragStarted);
}
document.addEventListener("mousemove", drag);
document.addEventListener("mouseup", dragStop);
function dragStarted(e) {
mouseX = e.pageX;
mouseY = e.pageY;
selectedArc = arcs[e.target.parentElement.getAttribute("data-linkedID")];
selectedArc.saveControlPoints();
dragging = true;
}
function drag(e) {
if (dragging) {
selectedArc.update(e.pageX - width, e.pageY - height, e.pageX - mouseX, e.pageY - mouseY);
update();
var arc;
for (var a = 0; a < points.length; a++) {
arc = arcs[a];
points[a].style.left = (arc.pointA.x - width) + "px";
points[a].style.top = (arc.pointA.y - height) + "px";
}
}
}
function dragStop(e) {
dragging = false;
}
function update() {
ctx.fillStyle = "#eeeeee";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
arcs.forEach(function(arc) {
ctx.moveTo(arc.pointA.x, arc.pointA.y);
ctx.bezierCurveTo(arc.controlPointA.x, arc.controlPointA.y, arc.controlPointB.x, arc.controlPointB.y, arc.pointB.x, arc.pointB.y);
});
ctx.stroke();
}
update();
#container {
position: absolute;
}
#canvas {
position: absolute;
top: 0px;
left: 0px;
}
.point {
position: absolute;
width: 10px;
height: 10px;
}
<div id="container">
<canvas id="canvas" width=300 height=300></canvas>
<svg class="point" id="pointA">
<circle cx="5" cy="5" r="5" fill="red" />
</svg>
<svg class="point" id="pointB">
<circle cx="5" cy="5" r="5" fill="red" />
</svg>
<svg class="point" id="pointC">
<circle cx="5" cy="5" r="5" fill="red" />
</svg>
<svg class="point" id="pointD">
<circle cx="5" cy="5" r="5" fill="red" />
</svg>
</div>
I have an SVG map and simple plugin which adds zoom and drag functionalities.
<svg>
<g class="main-container draggable" transform="matrix(1 0 0 1 0 0)">
<path id="AT-1" title="Burgenland" class="land" d=".../>
</g>
</svg>
const maxScale = 5,
minScale = 0.15;
var selected,
scale = 1,
svg = document.querySelector('svg');
function beginDrag(e) {
e.stopPropagation();
let target = e.target;
if (target.classList.contains('draggable')) {
selected = target;
} else {
selected = document.querySelector('.main-container');
}
selected.dataset.startMouseX = e.clientX;
selected.dataset.startMouseY = e.clientY;
}
function drag(e) {
if (!selected) return;
e.stopPropagation();
let startX = parseFloat(selected.dataset.startMouseX),
startY = parseFloat(selected.dataset.startMouseY),
dx = (e.clientX - startX),
dy = (e.clientY - startY);
if (selected.classList.contains('draggable')) {
let selectedBox = selected.getBoundingClientRect(),
boundaryBox = selected.parentElement.getBoundingClientRect();
if (selectedBox.right + dx > boundaryBox.right) {
dx = (boundaryBox.right - selectedBox.right);
} else if (selectedBox.left + dx < boundaryBox.left) {
dx = (boundaryBox.left - selectedBox.left);
}
if (selectedBox.bottom + dy > boundaryBox.bottom) {
dy = (boundaryBox.bottom - selectedBox.bottom);
}
else if (selectedBox.top + dy < boundaryBox.top) {
dy = (boundaryBox.top - selectedBox.top);
}
}
let currentMatrix = selected.transform.baseVal.consolidate().matrix,
newMatrix = currentMatrix.translate(dx / scale, dy / scale),
transform = svg.createSVGTransformFromMatrix(newMatrix);
selected.transform.baseVal.initialize(transform);
selected.dataset.startMouseX = dx + startX;
selected.dataset.startMouseY = dy + startY;
}
function endDrag(e) {
e.stopPropagation();
if (selected) {
selected = undefined;
}
}
function zoom(e) {
e.stopPropagation();
e.preventDefault();
let delta = e.wheelDelta,
container = document.querySelector('svg .main-container'),
scaleStep = delta > 0 ? 1.25 : 0.8;
if (scale * scaleStep > maxScale) {
scaleStep = maxScale / scale;
}
if (scale * scaleStep < minScale) {
scaleStep = minScale / scale;
}
scale *= scaleStep;
let box = svg.getBoundingClientRect();
let point = svg.createSVGPoint();
point.x = e.clientX - box.left;
point.y = e.clientY - box.top;
let currentZoomMatrix = container.getCTM();
point = point.matrixTransform(currentZoomMatrix.inverse());
let matrix = svg.createSVGMatrix()
.translate(point.x, point.y)
.scale(scaleStep)
.translate(-point.x, -point.y);
let newZoomMatrix = currentZoomMatrix.multiply(matrix);
container.transform.baseVal.initialize(svg.createSVGTransformFromMatrix(newZoomMatrix));
console.log("scale", scale);
let t = newZoomMatrix;
console.log("zoomMatrix", t.a, t.b, t.c, t.d, t.e, t.f);
}
document.querySelector('svg').addEventListener('mousedown', beginDrag);
document.querySelector('svg').addEventListener('mousewheel', zoom);
svg.addEventListener('mousemove', drag);
window.addEventListener('mouseup', endDrag);
At first sight, it works fine, however, it behaves strangely in some situations.
For example - if you zoom out I can freely drag it to any directions without any problem.
But if I zoom in to the scale that part of the map exceeds the parent element, any attempt to move it causes a jump of the entire map and blocks this functionality.
The second thing is - right now I can only move the map in the borders of SVG element and I want to have a possibility to drag it out outside it in the same way it works here: https://www.amcharts.com/svg-maps/?map=austria
Here is a snippet with my code: https://jsfiddle.net/marektchas/qo1eynag/3/
Posting here easy way for pan zoom functionality.
var svgCanvas = document.getElementById("canvas");
var viewPort = document.getElementById("viewport");
var drag = false;
var offset = { x: 0, y: 0 };
var factor = .1;
var matrix = new DOMMatrix();
svgCanvas.addEventListener('pointerdown', function (event) {
drag = true;
offset = { x: event.offsetX, y: event.offsetY };
});
svgCanvas.addEventListener('pointermove', function (event) {
if (drag) {
var tx = event.offsetX - offset.x;
var ty = event.offsetY - offset.y;
offset = {
x: event.offsetX,
y: event.offsetY
};
matrix.preMultiplySelf(new DOMMatrix()
.translateSelf(tx, ty));
viewPort.style.transform = matrix.toString();
}
});
svgCanvas.addEventListener('pointerup', function (event) {
drag = false;
});
svgCanvas.addEventListener('wheel', function (event) {
var zoom = event.deltaY > 0 ? -1 : 1;
var scale = 1 + factor * zoom;
offset = {
x: event.offsetX,
y: event.offsetY
};
matrix.preMultiplySelf(new DOMMatrix()
.translateSelf(offset.x, offset.y)
.scaleSelf(scale, scale)
.translateSelf(-offset.x, -offset.y));
viewPort.style.transform = matrix.toString();
});
html,
body {
height: 100%;
margin: 0;
box-sizing: border-box;
font-size: 62.5%;
}
body{
display: flex;
}
#around{
display: flex;
width: 100%;
border: 1px dashed orange;
}
#canvas{
flex: 1;
height: auto;
}
<div id="around">
<svg id="canvas" style="border: 1px solid blue;">
<g id="viewport">
<rect x="100" y="100" width="200" height="200" fill="red"/>
</g>
</svg>
</div>
It seems I found the solution however, I'm not quite sure how does it exactly work.
It works as expected if I remove .draggable class from the g element
After resizing the square, there is a collision problem, GIF animation problem, sample https://jsfiddle.net/8jkxdhfv/. What can i do? Should i untransformed mouse coordinates to transformed coordinates ? But how? How can i update x and y in my collision function?
HTML
<canvas id="test" width="480" height="380"></canvas>
<div id="text">Use mouse wheel to change square size</div>
JAVASCRIPT
var ctx = test.getContext('2d');
var obj = { x:100,y: 100,width: 100,height: 100}
var mouse = {x:0, y:0, width:10, height:10};
var zoom = 1;
setInterval(function(){
ctx.clearRect(0,0,test.width,test.height);
ctx.save();
var cx = obj.x+obj.width/2;
var cy = obj.y+obj.height/2;
// draw
ctx.translate(cx, cy);
ctx.scale(zoom,zoom);
ctx.translate(-cx,-cy);
ctx.fillRect(obj.x,obj.y,obj.width,obj.height);
ctx.restore();
// check collision
if(collision(obj,mouse)){
ctx.fillText("===== COLLISION =====", 110,90);
}
},1000/60);
function collision(obj1,obj2){
if(obj1.x < obj2.x + obj2.width * zoom &&
(obj1.x + obj1.width * zoom) > obj2.x &&
obj1.y < obj2.y + obj2.height * zoom &&
(obj1.height * zoom + obj1.y) > obj2.y){
return true;
}
return false;
}
window.addEventListener('mousewheel', function(e){
if(e.deltaY>0 && zoom<2){
zoom+=0.5;
}
if(e.deltaY<0 && zoom>0.5){
zoom-=0.5;
}
}, false);
window.addEventListener('mousemove', function(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
}, false);
You are getting mouse position based on entire window, not canvas. Some math and you will get what you want.
test.addEventListener("mousemove", function(evt) {
var mousePos = getMousePos(test, evt);
mouse.x = mousePos.x;
mouse.y = mousePos.y;
});
function getMousePos(canvas, event) {
var rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}
I have updated the function and it works:
function collision(obj1,obj2){
var eW = (obj1.width-(obj1.width*zoom))/2;
var eH = (obj1.height-(obj1.height*zoom))/2;
//console.log(eW);
if(obj1.x+eW < obj2.x + obj2.width * zoom &&
(obj1.x + obj1.width * zoom) + eW> obj2.x &&
obj1.y + eH < obj2.y + obj2.height * zoom &&
(obj1.height * zoom + obj1.y) + eH > obj2.y){
return true;
}
return false;
}
I am trying to draw a scalloped rectangle with mouse like in below image
I am able to draw rectangle with code below
function Rectangle(start, end) {
var w = (end.x - start.x);
var h = (end.y - start.y);
return ["M", start.x, start.y, "L", (start.x + w), start.y, "L", start.x + w, start.y + h, "L", start.x, start.y + h, "L", start.x, start.y].join(' ');
}
var point;
document.addEventListener('mousedown', function(event) {
point = {
x: event.clientX,
y: event.clientY
}
});
document.addEventListener('mousemove', function(event) {
var target = {
x: event.clientX,
y: event.clientY
}
if(point) {
var str = Rectangle(point, target);
document.getElementById('test').setAttribute('d', str);
}
});
document.addEventListener('mouseup', function(event) {
point = null;
});
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0
}
svg {
height:100%;
width: 100%
}
<svg>
<path id="test" style="stroke-width: 4; stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>
But when I try to convert into scalloped rectangle I am seeing different patterns exactly matching Infinite Monkey Theorm
The approach I tried is, draw a rectangular path on a virtual element. Take each point at length multiplied by 15 till total length. Then drawing arcs between those points. It is not working. Also, I want to avoid using getPointAtLength because it's mobile support is not great.
var pathEle = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pathEle.setAttribute('d', rectangle(point, target));
window.pathEle = pathEle;
var points = [];
for (var i = 0; i < pathEle.getTotalLength(); i += 15) {
points.push(pathEle.getPointAtLength(i));
}
document.getElementById('test1').setAttribute('d', toSVGPath(points));
Something like this?
I'm using arcs to make the scallops. You may want to tweak how the scallops are calculated to get better corners. But I'll leave that to you.
var scallopSize = 30;
function Rectangle(start, end) {
var minX = Math.min(start.x, end.x);
var minY = Math.min(start.y, end.y);
var w = Math.abs(end.x - start.x);
var h = Math.abs(end.y - start.y);
// Calculate scallop sizes
var numW = Math.round(w / scallopSize);
if (numW === 0) numW = 1;
var numH = Math.round(h / scallopSize);
if (numH === 0) numH = 1;
var stepW = w / numW;
var stepH = h / numH;
// top
var p = minX + stepW/2; // start each size at half a scallop along
var path = ["M", p, minY];
for (var i=1; i < numW; i++) { // numW-1 scallops per side
p += stepW;
path.push('A');
path.push(stepW/2 + 1); // Add 1 to the radius to ensure it's
path.push(stepW/2 + 1); // big enough to span the stepW
path.push("0 0 1");
path.push(p);
path.push(minY);
}
// top right
var p = minY + stepH/2;
path.push('A');
path.push(stepH/2.8); // 2 * sqrt(2)
path.push(stepH/2.8); // corners are a little smaller than the scallops
path.push("0 0 1");
path.push(minX + w);
path.push(p);
// right
for (var i=1; i < numH; i++) {
p += stepH;
path.push('A');
path.push(stepH/2 + 1);
path.push(stepH/2 + 1);
path.push("0 0 1");
path.push(minX + w);
path.push(p);
}
// bottom right
var p = minX + w - stepW/2;
path.push('A');
path.push(stepH/2.8);
path.push(stepH/2.8);
path.push("0 0 1");
path.push(p);
path.push(minY + h);
// bottom
for (var i=1; i < numW; i++) {
p -= stepW;
path.push('A');
path.push(stepW/2 + 1);
path.push(stepW/2 + 1);
path.push("0 0 1");
path.push(p);
path.push(minY + h);
}
// bottom left
var p = minY + h - stepH/2;
path.push('A');
path.push(stepH/2.8);
path.push(stepH/2.8);
path.push("0 0 1");
path.push(minX);
path.push(p);
// left
for (var i=1; i < numH; i++) {
p -= stepH;
path.push('A');
path.push(stepH/2 + 1);
path.push(stepH/2 + 1);
path.push("0 0 1");
path.push(minX);
path.push(p);
}
// top left
path.push('A');
path.push(stepH/2.8);
path.push(stepH/2.8);
path.push("0 0 1");
path.push(minX + stepW/2);
path.push(minY);
path.push('Z');
return path.join(' ');
}
var point;
document.addEventListener('mousedown', function(event) {
point = {
x: event.clientX,
y: event.clientY
}
});
document.addEventListener('mousemove', function(event) {
var target = {
x: event.clientX,
y: event.clientY
}
if(point) {
var str = Rectangle(point, target);
document.getElementById('test').setAttribute('d', str);
}
});
document.addEventListener('mouseup', function(event) {
point = null;
});
body, html {
height: 100%;
width: 100%;
margin: 0;
padding: 0
}
svg {
height:100%;
width: 100%
}
<svg>
<path id="test" style="stroke-width: 4; stroke: RGBA(212, 50, 105, 1.00); fill: none" />
</svg>
I want to add 4 text boxes which will give me the coordinates of a rectangle and if I manually edit the coordinates it should change /alter the rectangle as well.
Please tell me how to proceed with this solution.
In my example if you click ROI it will draw a rectangle I want the upper and lower X and Y coordinates of the same.
the working fiddle is http://jsfiddle.net/qf6Ub/2/
// references to canvas and context
var oImageBuffer = document.createElement('img');
var oCanvas = document.getElementById("SetupImageCanvas");
var o2DContext = oCanvas.getContext("2d");
// set default context states
o2DContext.lineWidth = 1;
o2DContext.translate(0.50, 0.50); // anti-aliasing trick for sharper lines
// vars to save user drawings
var layers = [];
var currentColor = "black";
// vars for dragging
var bDragging = false;
var startX, startY;
// vars for user-selected status
var $roiCheckbox = document.getElementById("btnROI");
var $layersCheckbox = document.getElementById("btnLAYER");
var $patches = document.getElementById('txtPatchCount');
var $mouse = document.getElementById("MouseCoords");
var roiIsChecked = false;
var layersIsChecked = false;
var patchCount = 0;
// listen for mouse events
oCanvas.addEventListener('mousedown', MouseDownEvent, false);
oCanvas.addEventListener('mouseup', MouseUpEvent, false);
oCanvas.addEventListener('mousemove', MouseMoveEvent, false);
oCanvas.addEventListener('mouseout', MouseOutEvent, false);
$("#txtPatchCount").keyup(function () {
getStatus();
// clear the canvas
o2DContext.clearRect(0, 0, oCanvas.width, oCanvas.height);
// redraw all previously saved line-pairs and roi
for (var i = 0; i < layers.length; i++) {
var layer = layers[i];
if (layer.patchCount > 0) {
layer.patchCount = patchCount;
}
draw(layer);
}
});
// mouse event handlers
function MouseDownEvent(e) {
e.preventDefault();
startX = e.clientX - this.offsetLeft;
startY = e.clientY - this.offsetTop;
currentColor = randomColor();
getStatus();
bDragging = true;
}
function MouseUpEvent(e) {
if (!bDragging) {
return;
}
e.preventDefault();
bDragging = false;
mouseX = e.clientX - this.offsetLeft;
mouseY = e.clientY - this.offsetTop;
layers.push({
x1: startX,
y1: startY,
x2: mouseX,
y2: mouseY,
color: currentColor,
drawLayer: layersIsChecked,
patchCount: patchCount,
});
}
function MouseOutEvent(e) {
MouseUpEvent(e);
}
function MouseMoveEvent(e) {
if (!bDragging) {
return;
}
var mouseX = e.clientX - this.offsetLeft;
var mouseY = e.clientY - this.offsetTop;
// clear the canvas
o2DContext.clearRect(0, 0, oCanvas.width, oCanvas.height);
// redraw all previously saved line-pairs and roi
for (var i = 0; i < layers.length; i++) {
draw(layers[i]);
}
// create a temporary layer+roi object
var tempLayer = {
x1: startX,
y1: startY,
x2: mouseX,
y2: mouseY,
color: currentColor,
drawLayer: layersIsChecked,
patchCount: patchCount,
};
// draw the temporary layer+roi object
draw(tempLayer);
// Display the current mouse coordinates.
$mouse.innerHTML = "(" + mouseX + "," + mouseY + ")" + patchCount;
}
function draw(layer) {
if (layer.drawLayer) {
// set context state
o2DContext.lineWidth = 0.50;
o2DContext.strokeStyle = layer.color;
// draw parallel lines
hline(layer.y1);
hline(layer.y2);
}
if (layer.patchCount > 0) {
// set context state
o2DContext.lineWidth = 1.5;
o2DContext.strokeStyle = '#0F0';
// draw regions
o2DContext.strokeRect(layer.x1, layer.y1, (layer.x2 - layer.x1), (layer.y2 - layer.y1));
var w = layer.x2 - layer.x1;
o2DContext.beginPath();
for (var i = 1; i < layer.patchCount; i++) {
var x = layer.x1 + i * w / layer.patchCount;
o2DContext.moveTo(x, layer.y1);
o2DContext.lineTo(x, layer.y2);
}
o2DContext.stroke();
}
}
function getStatus() {
roiIsChecked = $roiCheckbox.checked;
layersIsChecked = $layersCheckbox.checked;
patchCount = $patches.value;
if (!roiIsChecked || !patchCount) {
patchCount = 0;
}
}
function randomColor() {
return ('#' + Math.floor(Math.random() * 16777215).toString(16));
}
function hline(y) {
o2DContext.beginPath();
o2DContext.moveTo(0, y);
o2DContext.lineTo(oCanvas.width, y);
o2DContext.stroke();
}
document.getElementById("MouseCoords").innerHTML = "(" + x + "," + y + "); "
+"("+ oPixel.x + "," + oPixel.y + "); "
+"("+ oCanvasRect.left + "," + oCanvasRect.top + ")";
}
Ok, I went back to the drawing board and came up with this FIDDLE.
It provides the dimensions of the div and its location from the top and left of the container.
You can calculate the exact coordinates from those numbers.
JS
var divwidth = $('.coord').width();
var divheight = $('.coord').height();
var pos = $('.coord').offset();
$('#divdimensions').html(divwidth + ',' + divheight);
$('#divposition').html( pos.left + ',' + pos.top );