Change start point of circle in Canvas? - javascript

I found this code to type on an arc in a Canvas element. I've modified it basically to what I need, but I'm not sure how to change the "starting point" of it?
I've got a demo here

circle = {
x: cnv.width / 2, // x coordinate of center
y: cnv.height / 2, // y coordinate of center
r: 150,
lw: 75
};
for example, giving as given below will plot it at the origin of the canvas
circle = {
x: 0, // x coordinate of center
y: 0, // y coordinate of center
r: 150,
lw: 75
};

Related

Rotate a group of shapes containing text whilst keeping text centered and horizontal

This is probably just maths.
I am using Konva to dynamically generate shapes, which I'm storing as a label. So there's a label which contains a textElement and a rectangle. I want to make sure text in that rectangle is always a) Centered horizontally and vertically and b) facing the right way up.
So a rectangle could have any rotation, but I always want the text centered and facing the right way up.
The code for creation; width, height, rotation, x and y all have values pulled from a database.
var table = new Konva.Label({
x: pos_x,
y: pos_y,
width: tableWidth,
height: tableHeight,
draggable:true
});
table.add(new Konva.Rect({
width: tableWidth,
height: tableHeight,
rotation: rotation,
fill: fillColor,
stroke: strokeColor,
strokeWidth: 4
}));
table.add(new Konva.Text({
width: tableWidth,
height: tableHeight,
x: pos_x, //Defaults to zero
y: pos_y, //Default to zero
text: tableNumber,
verticalAlign: 'middle',
align: 'center',
fontSize: 30,
fontFamily: 'Calibri',
fill: 'black'
}))
tableLayer.add(table);
The problem is, if rotation is in place, text is off center, as in this image:
I do manually correct in some circumstances - for example if rotation = 45 degrees:
pos_x = -tableWidth/2;
pos_y = tableHeight/5;
but that is not a permanent solution. I want the x and y co-ordinates of the text to be at the centerpoint of the shape itself.
I've tried a few approaches (such as applying rotation to the Label itself and then negative rotation value to the text)
This code snippet illustrates a solution. It is copied & modified from my other self-answer when I was looking for a robust approach to rotation around an arbitrary point - note that I consider this a slightly different question than my original so I have not suggested this is a dup. The difference is the need to work with a more complex grouped shape and to keep some element within that group unrotated.
Not in the OP's question, but I set a background rectangle into the text by making the text a group. The purpose of this was to show that the text rectangle will extend outside the label rectangle in some points of rotation. This is not a critical issue but it is useful to see it happen.
The fundamental challenge for the coder is to understand how the shapes move when rotated since we usually want to spin them around their centre but the fundamental 2D canvas pattern that Konva (and all HTML5 canvas wrappers) follow is to rotate from the top-left corner, al least for rectangles as per shapes in the question. It 'is' possible to move the rotation point (known as the offset) but again that is a conceptual challenge for the dev and a nice trap for anyone trying to support the code later.
There's a lot of code in this answer that is here to set up something dynamic that you can use to visualise what is going on. However, the crux is in this:
// This is the important call ! Cross is the rotation point as illustrated by crosshairs.
rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
// The label is a special case because we need to keep the text unrotated.
if (shape.name() === 'label'){
let text = shape.find('.text')[0];
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
}
The rotateAroundPoint() function takes as parameters the Konva shape to rotate, the clockwise rotation angle (not radians, good ole degrees), and the x & y position of the rotation point on the canvas / parent.
I constructed a group of shapes as my label, composing it from a rectangle and a text shape. I named this 'label'. Actually I switched the text shape to be another group of rect + text to that I could show the rectangle the text sits within. You could leave out the extra group. I named this 'text'.
The first call to rotateAroundPoint() rotates the group named 'label'. So the group rotates on the canvas. Since the 'text' is a child of the 'label' group, that would leave the 'text' rotated, so the next line checks if we are working with the 'label' group, and if so we need to get hold of the 'text' shape which is what this line does:
let text = shape.find('.text')[0];
In Konva the result of a find() is a list so we take the first in the list. Now all that remains for me to do is rotate the text on the 'label' group back again by applying the negative rotation degrees to its center point. The line below achieves this.
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
One note worthy of mention - I used a group for my 'text' shape. A Konva group does not naturally have a width or height - it is more of a means to collect shapes together but without a 'physical' container. So to get its width and height for the centre point calculations I use the group.getClientRect() method which gives the size of the minimum bounding box that would contain all shapes in the group, and yields an object formed as {width: , height: }.
Second note - the first use of rotateAroundPoint() affects the 'label' group which has as its parent the canvas. The second use of that function affects the 'text' group which has the 'label' group as its parent. Its subtle but worth knowing.
Here is the snippet. I urge you to run it fullscreen and spin a few shapes around a few different points.
// Code to illustrate rotation of a shape around any given point. The important functions here is rotateAroundPoint() which does the rotation and movement math !
let
angle = 0, // display value of angle
startPos = {x: 80, y: 45},
shapes = [], // array of shape ghosts / tails
rotateBy = 20, // per-step angle of rotation
shapeName = $('#shapeName').val(), // what shape are we drawing
shape = null,
ghostLimit = 10,
// Set up a stage
stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
}),
// add a layer to draw on
layer = new Konva.Layer(),
// create the rotation target point cross-hair marker
lineV = new Konva.Line({points: [0, -20, 0, 20], stroke: 'lime', strokeWidth: 1}),
lineH = new Konva.Line({points: [-20, 0, 20, 0], stroke: 'lime', strokeWidth: 1}),
circle = new Konva.Circle({x: 0, y: 0, radius: 10, fill: 'transparent', stroke: 'lime', strokeWidth: 1}),
cross = new Konva.Group({draggable: true, x: startPos.x, y: startPos.y}),
labelRect, labelText;
// Add the elements to the cross-hair group
cross.add(lineV, lineH, circle);
layer.add(cross);
// Add the layer to the stage
stage.add(layer);
$('#shapeName').on('change', function(){
shapeName = $('#shapeName').val();
shape.destroy();
shape = null;
reset();
})
// Draw whatever shape the user selected
function drawShape(){
// Add a shape to rotate
if (shape !== null){
shape.destroy();
}
switch (shapeName){
case "rectangle":
shape = new Konva.Rect({x: startPos.x, y: startPos.y, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "hexagon":
shape = new Konva.RegularPolygon({x: startPos.x, y: startPos.y, sides: 6, radius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "ellipse":
shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 20, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "circle":
shape = new Konva.Ellipse({x: startPos.x, y: startPos.y, radiusX: 40, radiusY: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "star":
shape = new Konva.Star({x: startPos.x, y: startPos.y, numPoints: 5, innerRadius: 20, outerRadius: 40, fill: 'magenta', stroke: 'black', strokeWidth: 4});
break;
case "label":
shape = new Konva.Group({name: 'label'});
labelRect = new Konva.Rect({x: 0, y: 0, width: 120, height: 80, fill: 'magenta', stroke: 'black', strokeWidth: 4, name: 'rect'})
shape.add(labelRect);
labelText = new Konva.Group({name: 'text'});
labelText.add(new Konva.Rect({x: 0, y: 0, width: 100, height: 40, fill: 'cyan', stroke: 'black', strokeWidth: 2}))
labelText.add(new Konva.Text({x: 0, y: 0, width: 100, height: 40, text: 'Wombat',fontSize: 20, fontFamily: 'Calibri', align: 'center', padding: 10}))
shape.add(labelText)
labelText.position({x: (labelRect.width() - labelText.getClientRect().width) /2, y: (labelRect.height() - labelText.getClientRect().height) /2})
break;
};
layer.add(shape);
cross.moveToTop();
}
// Reset the shape position etc.
function reset(){
drawShape(); // draw the current shape
// Set to starting position, etc.
shape.position(startPos)
cross.position(startPos);
angle = 0;
$('#angle').html(angle);
$('#position').html('(' + shape.x() + ', ' + shape.y() + ')');
clearTails(); // clear the tail shapes
stage.draw(); // refresh / draw the stage.
}
// Click the stage to move the rotation point
stage.on('click', function (e) {
cross.position(stage.getPointerPosition());
stage.draw();
});
// Rotate a shape around any point.
// shape is a Konva shape
// angleRadians is the angle to rotate by, in radians
// point is an object {x: posX, y: posY}
function rotateAroundPoint(shape, angleDegrees, point) {
let angleRadians = angleDegrees * Math.PI / 180; // sin + cos require radians
const x =
point.x +
(shape.x() - point.x) * Math.cos(angleRadians) -
(shape.y() - point.y) * Math.sin(angleRadians);
const y =
point.y +
(shape.x() - point.x) * Math.sin(angleRadians) +
(shape.y() - point.y) * Math.cos(angleRadians);
shape.rotation(shape.rotation() + angleDegrees); // rotate the shape in place
shape.x(x); // move the rotated shape in relation to the rotation point.
shape.y(y);
shape.moveToTop(); //
}
$('#rotate').on('click', function(){
let newShape = shape.clone();
shapes.push(newShape);
layer.add(newShape);
// This ghost / tails stuff is just for fun.
if (shapes.length >= ghostLimit){
shapes[0].destroy();
shapes = shapes.slice(1);
}
for (var i = shapes.length - 1; i >= 0; i--){
shapes[i].opacity((i + 1) * (1/(shapes.length + 2)))
};
// This is the important call ! Cross is the rotation point as illustrated by crosshairs.
rotateAroundPoint(shape, rotateBy, {x: cross.x(), y: cross.y()});
// The label is a special case because we need to keep the text unrotated.
if (shape.name() === 'label'){
let text = shape.find('.text')[0];
rotateAroundPoint(text, -1 * rotateBy, {x: text.getClientRect().width/2, y: text.getClientRect().height/2});
}
cross.moveToTop();
stage.draw();
angle = angle + 10;
$('#angle').html(angle);
$('#position').html('(' + Math.round(shape.x() * 10) / 10 + ', ' + Math.round(shape.y() * 10) / 10 + ')');
})
// Function to clear the ghost / tail shapes
function clearTails(){
for (var i = shapes.length - 1; i >= 0; i--){
shapes[i].destroy();
};
shapes = [];
}
// User cicks the reset button.
$('#reset').on('click', function(){
reset();
})
// Force first draw!
reset();
body {
margin: 10;
padding: 10;
overflow: hidden;
background-color: #f0f0f0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://unpkg.com/konva#^3/konva.min.js"></script>
<p>1. Click the rotate button to see what happens when rotating around shape origin.</p>
<p>2. Reset then click stage to move rotation point and click rotate button again - rinse & repeat</p>
<p>
<button id = 'rotate'>Rotate</button>
<button id = 'reset'>Reset</button>
<select id='shapeName'>
<option value='label' selected='selected'>Label</option>
<option value='rectangle'>Rectangle</option>
<option value='hexagon'>Polygon</option>
<option value='ellipse' >Ellipse</option>
<option value='circle' >Circle</option>
<option value='star'>Star</option>
</select>
Angle : <span id='angle'>0</span>
Position : <span id='position'></span>
</p>
<div id="container"></div>

Dojo connect doesn't work with dojo declare

I have built a square and a rectangle. I use dojo.declare to overwrite the onMouseMove event, so that the square only is dnd inside the surface.
But i have problem to connect the square with a function.
The rectangle doesn't use the custom mover and is connected to a function. It prints out to the console every time it moves.
What am i doing wrong ?
http://jsfiddle.net/Baboly/jh6dnb49/
HTML:
<body>
<div id='drawing_surface'></div>
</body>
Javascript:
djConfig='parseOnLoad: true'>
dojo.require('dojox.gfx'); // omit this and the rectangle doesn't draw
dojo.require('dojox.gfx.move'); // omit this and the rectangle doesn't move
dojo.require('dojo.parser'); // parser and dojo.css aren't required
dojo.ready(function() {
surface = dojox.gfx.createSurface(dojo.byId('drawing_surface'),'640px', '550px');
// draw rectangle on the surface
// createRect({}) parameters
// x: horizontal offset from left side of surface
// y: vertical offset from top of surface
// height, width: dimensions of rectangle
rectangle = surface.createRect({ x: 20, y: 5, height: 100, width: 150})
.setFill([0,0,255,0.3])
.setStroke({ color: "blue", width: 2})
;
var symbol = surface.createGroup();
square = surface.createRect({ x: 20, y: 10, height: 100, width: 100})
.setFill([0,0,255,0.3])
.setStroke({ color: "blue", width: 2})
;
// define limits to motion - in this case, the entire drawing surface.
var limits = { xmin: 0, xmax: 640, ymin: 0, ymax: 550};
// Extend mover with a class that overwites onMouseMove.
// We will call the class net.aa3sd.surfaceMover to prevent naming
// conflicts with any existing dojo classes
dojo.declare("net.aa3sd.surfaceMover",dojox.gfx.Mover, {
// Mover has several methods, we only need to overwrite onMouseMove.
onMouseMove: function(event) {
// getTransform() will tell us how far the shape has been moved
// from its initial position
transform = this.shape.getTransform();
// If this.shape hasn't been transformed yet
// getTransform() will return null, so we
// need to handle the special case of the first mouse movement.
if (transform==null) {
// We aren't dealing with rotations here, so we will
// just define initial values for the translations dx and dy.
transform = { dx: 0, dy: 0 };
}
// event.layerX is the mouse position in the coordinate system of the
// layer that was clicked on, the rectangle in this case
var layerx = event.layerX;
var layery = event.layerY;
if (this.click_on_rectangle==null) {
// we need to find where in the rectangle the mouse pointer is
// otherwise the rectangle will drag untill the mouse pointer
// hits the limits, rather than when the rectangel hits
// the limits.
this.click_on_rectangle = {
x: layerx - this.shape.shape.x - transform.dx,
y: layery - this.shape.shape.y - transform.dy
} // x,y of initial mouse click in coordinate system of rectangle
}
var click_on_surface = {
x: layerx - this.click_on_rectangle.x,
y: layery - this.click_on_rectangle.y
}; // x,y of mouse click in coordinate system of drawing surface
var x = event.clientX;
var y = event.clientY;
// check to see if the edges of the rectangle are within the
// limits of movement, if so, allow the rectangle to be moved
if (click_on_surface.x > limits.xmin
& click_on_surface.y > limits.ymin
& click_on_surface.x < limits.xmax - square.shape.width
& click_on_surface.y < limits.ymax - square.shape.height
) {
// move the rectangle by applying a translation
this.shape.applyLeftTransform({
dx: x - this.lastX,
dy: y - this.lastY
});
// store the last position of the rectangle
this.lastX = x;
this.lastY = y;
}
dojo.stopEvent(event);
}
});
// now we simply make the rectangle movable using the custom mover.
var recHandler = new dojox.gfx.Moveable(rectangle);
console.log(recHandler);
dojo.connect(recHandler, "onMove", function(mover) {
console.log("recHandler start moving with mover:", mover);
});
var sqHandler = new dojox.gfx.Moveable(square, { mover: net.aa3sd.surfaceMover })
console.log(sqHandler);
dojo.connect(sqHandler, "onMove", function(mover) {
console.log("sqHandler start moving with mover:", mover);
});
});

Raphael JS position rectangle based on center coordinates

Is there a way to position a rectangle in Raphael.js based on the center coordinates (much like positioning a circle)?
You can do it by simply creating your own custom element, here is an example on how it can be done :
Raphael.fn.MyRect = function( cx, cy, width, height ) {
var xLeftTop = cx - (width / 2);
var yLeftTop = cy - (height / 2);
this.rectObj = paper.rect( xLeftTop, yLeftTop, width, height );
return this;
};
var paper = Raphael(10, 50, 320, 200);
paper.MyRect( 95, 35, 50, 50 ); // 95 is the center in x and 35 the center in y
A live example : http://jsfiddle.net/7QMbH/
This way, you can create as many rectangle you want, and it makes your code understandable.
The easiest way is to create your rectangle with coordinates x - rect_width/2 and y - rect_heigth/2:
Example:
var rect_w = 180,
rect_h = 80;
var paper = Raphael(10, 10, 500, 500);
paper.rect(100 - rect_w/2, 100 - rect_h/2, rect_w, rect_h, 5);
// The last 5 gives curved edges to you rectangle (if you need it of course).
Basically, instead of 100,100 for top-left, you give 10,60. Good Luck

KineticJS: Rotate line around point

I'm trying to rotate a line around a point using KineticJS. But the line always rotates around the origin. I already tried using a offset, but it didn't work either.
The black point is normally positionable. I want the gray line to rotate in angles between 0 and 360 around the black point.
line.setPoints([{
x: stage.getWidth() / 2,
y: stage.getHeight() / 2
}, {
x: stage.getWidth() / 2,
y: stage.getHeight() / 2 - 30
}]);
line.transitionTo({
rotation: angle,
duration: 1,
easing: 'ease-out'
});
Any ideas?
I made a fiddle: http://jsfiddle.net/QuT4d/
http://jsfiddle.net/QuT4d/1/
So you need to set a position from the get go for your line, and set the points relative to the position:
line = new Kinetic.Line({
x: stage.getWidth() / 2, // <--- right here
y: stage.getHeight() / 2, // <--- and right here
points: [{
x: 0, // <--- if you start at 0, you start at the above x: which is the center
y: 0 // <--- if you start at 0, you start at the above y: which is the center
}, {
x: 0,
y: 0- 30
}],
stroke: 'gray',
strokeWidth: 3
});
Its not that it wasn't working, you were setting the position, then setting the points, which was really drawing your object off of the screen. You could see this happen if you set the transition time to a larger number and see it move off the screen slowly.
You also don't need to set the points again or reset the position.

Drawing shapes and lines with HTML5 Canvas and jQuery

I have an upcoming project that I would like to use the HTML5 canvas element to accomplish what would have had to be done in the past with either images and absolutely paced div's or Flash. Here is a basic example of the concept
Here are my concerns:
I am ok with using div's with corner radius to create the circles as they will be styled, and I'm not sure if I can do that with a mix of svg and the canvas element.
My main concern is the stroke that joins the outer circles to the inner, I would like to do this with canvas but am not sure A) How to get multiple canvas elements on one page in one containing element (a wrapper div) and B) how to figure out the starting points, I would assume the ending point would just be the center of the wrapper div (IE if its 600x600 = x=300, y=300)
Can anyone shed some light on this and offer any suggestions? Is there an advantage to using any of the jQuery canvas plugiins over vanilla JS?
thank you!
The canvas API consists of some functions which seem to do the job just fine:
.moveTo/.lineTo for a line path
.arc for a circle path
.stroke to stroke a path (line)
.fill to fill a path (circle)
Here's a very trivial proof of concept: http://jsfiddle.net/eGjak/275/.
I've used (x, y) for both the lines and the circles, which means the lines go from and to the midpoint of two circles. r is the radius of a circle.
var ctx = $('#cv').get(0).getContext('2d');
var circles = [ // smaller circles
{ x: 50, y: 50, r: 25 },
{ x: 250, y: 50, r: 25 },
{ x: 250, y: 250, r: 25 },
{ x: 50, y: 250, r: 25 },
];
var mainCircle = { x: 150, y: 150, r: 50 }; // big circle
function drawCircle(data) {
ctx.beginPath();
ctx.arc(data.x, data.y, data.r, 0, Math.PI * 2); // 0 - 2pi is a full circle
ctx.fill();
}
function drawLine(from, to) {
ctx.beginPath();
ctx.moveTo(from.x, from.y);
ctx.lineTo(to.x, to.y);
ctx.stroke();
}
drawCircle(mainCircle); // draw big circle
$.each(circles, function() { // draw small circles and lines to them
drawCircle(this);
drawLine(mainCircle, this);
});​
You could just do all of these circles in CSS. Get some divs, style them as you like in CSS, and then apply border-radius: 100; to the object, and done. I hope this helped.

Categories

Resources