I've got 4 arcs of a circle which are generated using SnapSVG.js
The problem is that between the upper arcs the gap is higher than the gap of lower arcs
So my question is can I fix the gap between arcs and obtain a perfect circle using only CSS to cosistency with resize?
This is a raw JS [I intend to clean it a bit]
http://jsfiddle.net/LtLafp2r/
var canvasSize = 200,
centre = canvasSize/2,
radius = canvasSize*0.8/2,
s = Snap('#svg'),
path = "",
arc = s.path(path),
startY = centre-radius;
var d = 0;
var dr =0;
radians = Math.PI*(dr)/180,
endx = centre + radius*Math.cos(radians),
endy = centre + radius * Math.sin(radians),
largeArc = d>180 ? 1 : 0;
var s = Snap("#svg");
// Lets create big circle in the middle:
path = "M"+centre+","+startY+" A"+radius+","+radius+" 0 "+largeArc+",1 "+endx+","+endy;
var arc = s.path(path);
// By default its black, lets change its attributes
arc.attr({
stroke: '#3da08d',
fill: 'none',
strokeWidth: 25
});
Maybe I'm misunderstanding but using this CSS does a perfect circle with your arcs :
svg{position:fixed;}
#svg3{left:-72px; top:88px;}
#svg4{left:88px; top:88px;}
http://jsfiddle.net/LtLafp2r/3/
Ps: there is a bug in rendering path arcs in Chrome : check this question
Related
After looking through similar questions posted to the forum and not finding something that helped me solve my own problem, I'm posting it.
I'm using SVG.js to generate SVG shapes in a web document. I'd like one of those shapes to ”follow” the mouse/cursor.
By that I mean: The shape has a fixed position/anchor point (at its original center) and it can only move a limited distance (let's say 50px) away from this fixed point.
I want the shape to move in the direction of the cursor, whenever the cursor moves, but never further than a defined distance away from its orignal position. I'm attaching a short animation to illustrate my description:
If the cursor were to disappear, the shape would snap back to its original center.
I know my way around Javascript, HTML and CSS. This type of element-manipulation is new to me and the math is giving my quite the headache, any help would be great.
It looks like I need the shape to basically rotate around its original center, with an angle relative to the cursor? I'm really unsure how to solve this. I have tried using a method to calculate the angle described in this post. My shape moves, but not as intended:
// init
var draw = SVG().addTo('body')
// draw
window.shape = draw.circle(25, 25).stroke({
color: '#000',
width: 2.5
}).fill("#fff");
shape.attr("id", "circle1");
shape.move(50, 50)
// move
var circle = $("#circle1");
var dist = 10;
$(document).mousemove(function(e) {
// angle
var circleCenter = [circle.offset().left + circle.width() / 2, circle.offset().top + circle.height() / 2];
var angle = Math.atan2(e.clientX - circleCenter[0], -(e.clientY - circleCenter[1])) * (180 / Math.PI);
var x = Math.sin(angle) * dist;
var y = (Math.cos(angle) * dist) * -1;
shape.animate().dmove(x, y);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Note: It does not matter to me whether the solution depends on jQuery or not (ideally it doesn't).
After more fiddling around with some solutions to calculating angles and distances, I found the answer.
I'm using a fixed reference point to calculate the angle of the direct line between the center of the shape and the cursor. Then I move the shape relative to this reference point and by a given amount:
// Init canvas
var draw = SVG().addTo('body')
// Draw reference/anchor
var shape_marker_center = draw.circle(3,3).fill("#f00").move(150, 150);;
var grafikCenter = [shape_marker_center.attr("cx"), shape_marker_center.attr("cy")]
// Draw shapes
var shape = draw.circle(25, 25).stroke({color: '#000', width: 2.5 }).fill("none");
shape.attr("id", "circle1").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
var shape2 = draw.circle(50, 50).stroke({color: '#000', width: 2.5 }).fill("none");
shape2.attr("id", "circle2").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
var shape3 = draw.circle(75, 75).stroke({color: '#000', width: 2.5 }).fill("none");
shape3.attr("id", "circle3").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
$(document).mousemove(function(e) {
var pointA = [shape_marker_center.attr("cx"), shape_marker_center.attr("cy")];
var pointB = [e.clientX, e.clientY];
var angle = Math.atan2(pointB[1] - pointA[1], pointB[0] - pointA[0]) * 180 / Math.PI ;
//
var distance_x_1 = Math.cos(angle*Math.PI/180) * 16;
var distance_y_1 = Math.sin(angle*Math.PI/180) * 16;
var distance_x_2 = Math.cos(angle*Math.PI/180) * 8;
var distance_y_2 = Math.sin(angle*Math.PI/180) * 8;
//
shape.center((grafikCenter[0] + distance_x_1), (grafikCenter[1] + distance_y_1));
shape2.center((grafikCenter[0] + (distance_x_2) ), (grafikCenter[1] + (distance_y_2)));
})
svg {
width: 100vw;
height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
d3.js verison 4:
I have a line chart, which should have a rectangle zoom.
I used this example: http://bl.ocks.org/jasondavies/3689931
I don't want to apply the rectangle data to the scales, like in the example
Instead I want to apply this to my normal zoom Element.
For that I have the math:
.on("mouseup.zoomRect", function() {
d3.select(window).on("mousemove.zoomRect", null).on("mouseup.zoomRect", null);
d3.select("body").classed("noselect", false);
var m = d3.mouse(e);
m[0] = Math.max(0, Math.min(width, m[0]));
m[1] = Math.max(0, Math.min(height, m[1]));
if (m[0] !== origin[0] && m[1] !== origin[1]) {
//different code here
//I have the scale factor
var zoomRectWidth = Math.abs(m[0] - origin[0]);
scaleFactor = width / zoomRectWidth;
//Getting the translation
var translateX = Math.min(m[0], origin[0]);
//Apply to __zoom Element
var t = d3.zoomTransform(e.node());
e.transition()
.duration(that.chart.animationDuration)
.call(that.chart.zoomX.transform, t
.translate(translateX, 0)
.scale(scaleFactor)
.translate(-translateX, 0)
);
}
rect.remove();
refresh();
}, true);
So I actually get the scaleFactor right and it zooms in smoothly.
Only problem is, that I don't seem to get the translation correct.
So it zooms in to the wrong position.
So, now I got it right:
All transformations by earlier zooms need to be undone.
so that k = 1, x = 0, y = 0;
This is the d3.zoomIdentity.
From that point the current zoom needs to be applied and afterwards the translation.
After that the old transform needs to be applied, first translate and then scale
var t = d3.zoomTransform(e.node());
//store x translation
var x = t.x;
//store scaling factor
var k = t.k;
//apply first rect zoom scale and then translation
//then old translate and old zoom scale
e.transition()
.call(that.chart.zoomX.transform, d3.zoomIdentity
.scale(scaleFactor)
.translate(-translateX, 0)
.translate(x)
.scale(k)
);
Working Fiddle only for X-Axis here: https://jsfiddle.net/9j4kqq1v/3/
Working fiddle for X and Y-axis here: https://jsfiddle.net/9j4kqq1v/5/
I'm trying to display numbers around the spokes of a bicycle wheel. In the process of creating the 'spokes' for the wheel I can't seem to get the text to rotate without messing up the rotation of the wheel.
var arr = ['1','2','3','4','5','1','2','3','4','5','1','2','3','4','5','1','2','3','4','5'];
function drawNumber() {
var cID = document.getElementById('cogs');
var canW = cID.width,
canH = cID.height;
if (cID && cID.getContext){
var ctx = cID.getContext('2d');
if(ctx) {
ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
var radian = (Math.PI / 180) * 18;
var i = 0
for (var degrees = 0; degrees < 360; degrees += 18) {
var fillNum = arr[i];
ctx.font = "12pt Arial"
ctx.fillStyle = 'white';
ctx.rotate(radian);
rotateText(fillNum);
i++;
}
ctx.translate(-canW/2, -canH/2);
}
}
}
function rotateText(i){
var cID = document.getElementById('cogs');
ctx = cID.getContext('2d');
ctx.fillText(i, -5, 150);
}
drawNumber();
http://jsfiddle.net/rdo64wv1/8/
If I add a rotate to the rotate text function it doesn't rotate the text, it just moves around the spokes further. Any ideas?
If I understand you correctly, you want to numbers to continue along the spoke direction at 90°. What you mean exactly is a bit unclear as what direction is text continuing at in the first place. Considering that the fiddle shows the text continuing at the y-axis, here is how to draw text with the text result continuing at the x-axis instead (if this is not what you're after, please include a mock-up of what result you expect - just adjust the angle at the commented-out line as needed).
Think of the process as an arm: shoulder is rotated first, then the elbow, both at their pivot points, but elbow is always relative to shoulder angle.
So, first rotate at center of wheel to get the spoke angle. Then translate to the origin of the text along that spoke (x-axis in canvas as 0° points right) to get to the "elbow" pivot point, or origin. Rotate (if needed) and draw text, finally reset transformation and repeat for next number.
Here's an updated example with some additional adjustments:
var arr = ['1','2','3','4','5','1','2','3','4','5','1','2','3','4','5','1','2','3','4','5'];
function drawNumber() {
var cID = document.getElementById('cogs'),
cx = cID.width * 0.5, // we'll only use the center value
cy = cID.height * 0.5;
if (cID && cID.getContext){
var ctx = cID.getContext('2d');
if(ctx) {
ctx.font = "12pt Arial" // set font only once..
ctx.textAlign = "center"; // align to center so we don't
ctx.textBaseline = "middle"; // need to calculate center..
var step = Math.PI * 2 / arr.length; // step based on array length (2xPI=360°)
for (var angle = 0, i = 0; angle < Math.PI * 2; angle += step) {
ctx.setTransform(1,0,0,1,cx, cy); // hard reset transforms + translate
ctx.rotate(angle); // absolute rotate wheel center
ctx.translate(cx - 10, 0); // translate along x axis
//ctx.rotate(-Math.PI*0.5); // 90°, if needed...
ctx.fillText(arr[i++], 0, 0); // draw at new origin
}
}
}
ctx.setTransform(1,0,0,1,0,0); // reset all transforms
}
drawNumber();
<canvas id='cogs' width='300' height='300'></canvas>
This is a box with an irregular shape that I have generated:
And this is the end effect I'd like to achieve (note the smooth edges):
Here's the code for my sharp version:
var path1 = new Path({
segments: [[123, 6], [290, 6], [304, 142], [112, 142]],
strokeColor: 'white',
closed: true,
strokeWidth: 3,
strokeJoin: 'round'
});
Thing is, I'm already using the strokeJoin: 'round' option and the difference is hardly noticeable with a stroke width of 3px. It's a small thing but could turn into a game breaker as there are going to be multiple objects like this and the difference is huge.
Is there any way to achieve that with paper.js without overdoing it?
As markE mentioned, strokeJoin only changes the canvas style of a path's stroke. Paper.js does not come with a corner-rounding function, you'll have to make your own.
Here's a quick function that you can use a starting point. It will negatively offset the points of a polygon by a given distance and add the appropriate handles.
function roundPath(path,radius) {
var segments = path.segments.slice(0);
path.segments = [];
for(var i = 0, l = segments.length; i < l; i++) {
var curPoint = segments[i].point;
var nextPoint = segments[i + 1 == l ? 0 : i + 1].point;
var prevPoint = segments[i - 1 < 0 ? segments.length - 1 : i - 1].point;
var nextDelta = curPoint - nextPoint;
var prevDelta = curPoint - prevPoint;
nextDelta.length = radius;
prevDelta.length = radius;
path.add({
point:curPoint - prevDelta,
handleOut: prevDelta/2
});
path.add({
point:curPoint - nextDelta,
handleIn: nextDelta/2
});
}
path.closed = true;
return path;
}
Here it is in action.
I was looking for an exact implementation, as described here: http://shanfanhuang.com/everything/2015/10/27/rounding-corners
My implementation works as follows:
curPoint is the corner, prevPoint and nextPoint as above
nextNorm and prevNorm are the normalized versions of the points
angle is the angle of the corner, derived from the dot product
delta is the distance from the corner points to where the control points need to be inserted, this is derived from a right triangle formed by the control point, curPoint and the center of the corner arc. The corner is a half angle, and the side opposing that corner is the radius
prevDelta and nextDelta are the new endpoints of the sides, between those an arc is inserted
through is a point halfway on the arc, found by getting the hypotenuse of the above triangle and subtracting the radius.
var segments = path.segments.slice(0);
path.segments = [];
for(var i = 0, l = segments.length; i < l; i++) {
var curPoint = segments[i].point;
var nextPoint = segments[i + 1 == l ? 0 : i + 1].point;
var prevPoint = segments[i - 1 < 0 ? segments.length - 1 : i - 1].point;
var nextNorm = (curPoint - nextPoint).normalize();
var prevNorm = (curPoint - prevPoint).normalize();
var angle = Math.acos(nextNorm.dot(prevNorm));
var delta = radius/Math.tan(angle/2);
var prevDelta = prevNorm.normalize(delta);
var nextDelta = nextNorm.normalize(delta);
var through = curPoint - (prevNorm + nextNorm).normalize(Math.sqrt(delta*delta + radius*radius) - radius);
path.add(curPoint - prevDelta);
path.arcTo(through, curPoint - nextDelta);
}
I've got a Raphael.js image that I've done, which consists of a set of circles, like so
var paper = Raphael(0,0,100,100);
var circle1 = paper.circ(20,20,10);
var circle2 = paper.circ(40,40,10);
I also have an svg icon in Raphael format (thanks to the lovely site http://readysetraphael.com/) that I'd like to place inside of each circle. Problem is... all the paths in the converted svg icon are now relative to the point (0,0) ! By this is mean all the strings are written like this
paper.path('M 1 1 L 2 0 L 0,2 z')
So my question ... is there some clever way to 'relativise' this path to make it sit inside each circle, without just going in and changing every single element of the path string by hand to make it draw the same path twice inside the two circles?
Try this. Replace the contents of shp with any other valid path.
var shape,
circleHalfWidth,
circleHalfHeight,
shpHalfHeight,
shpHalfWidth,
targetLeft,
targetTop,
paper,
circle1,
circBBox,
shpBBox,
shp,
radius = 20,
b2,
c2,
b,
s;
shape = "M25.979,12.896,5.979,12.896,5.979,19.562,25.979,19.562z";
paper = new Raphael(0,0,500,500);
circle1 = paper.circle(100,100,radius);
shp = paper.path( shape );
// get objects that contain dimensions of circle and shape
circBBox = circle1.getBBox( );
shpBBox = shp.getBBox( );
// get dimensions that will help us calculate coordinates to centre of circle
circleHalfWidth = circBBox.width / 2;
circleHalfHeight = circBBox.height / 2;
shpHalfWidth = shpBBox.width / 2;
shpHalfHeight = shpBBox.height / 2;
// calculate coordinates to position shape on top left corner of circle
targetLeft = circle1.getBBox( ).x - shp.getBBox( ).x;
targetTop = circle1.getBBox( ).y - shp.getBBox( ).y;
//Calculate how wide is shape allowed to be in circle using pythagoras
c2 = Math.pow( radius, 2 );
b2 = c2 / 2;
b = Math.sqrt( b2 );
// Calculate ratio so that both height and width fit in circle
s = shpBBox.width > shpBBox.height ? ( shpBBox.width / b ) : ( shpBBox.height / b );
// change coordinates so shape will be moved to centre of circle
targetLeft += circleHalfWidth - shpHalfWidth;
targetTop += circleHalfHeight - shpHalfHeight;
// Remember uppercase T so that scaling is ignored when moving shape
shp.transform( "s" + s + "T" + targetLeft + "," + targetTop );
fiddle here