I have a circle and simple function Math.cos(x)
I want the circle to be filled when it intersects with that function (fill only the upper side). But it's not working.
Script:
// circle
var point1 = app.board.create('point', [0,0], {size: 2, strokeWidth:2 })
var point2 = app.board.create('point', [6,0], {size: 2, strokeWidth:2 })
var circle = app.board.create('circle', [point1,point2], {strokeColor: "#f00", strokeWidth: 2 })
// function
var func = app.board.create('functiongraph',[function(x){ return Math.cos(x)}]);
// intersection
var curve = app.board.create('curve', [[], []], {strokeWidth: 0, fillColor: "#09f", fillOpacity: 0.8})
curve.updateDataArray = function() {
var a = JXG.Math.Clip.intersection(circle, func, this.board);
this.dataX = a[0];
this.dataY = a[1]
};
app.board.update()
Output
Expected output (I did it on Paint)
Thank you in advance :)
This can easily realized with the next version of JSXGraph which will be released next week: With the inequality element the area above the cosine curve can be marked. The inequality element is a closed curve and can be intersected with a circle. In v1.2.3, the intersection does not work because of a small bug.
For the clipping, the next version contains new elements curveintersection, curveunion, curvedifference which make it easier to use the methods of JXG.Math.Clip, but of course your approach with JXG.Math.Clip will still work.
Here is the code:
var f = board.create('functiongraph', ['cos(x)']);
var ineq = board.create('inequality', [f], {
inverse: true, fillOpacity: 0.1
});
var circ = board.create('circle', [[0,0], 4]);
var clip = board.create('curveintersection', [ineq, circ], {
fillColor: 'yellow', fillOpacity: 0.6
});
Actually, the inequality element does the same as enxaneta does "by hand".
In the next example I'm building the d attribute for the path using Math.cos().
I suppose your function may be different.
Please observe that at the end at the d attribute the path is closing the upper part of the svg canvas.
I'm using the pth inside a clipPath and I'm clipping the circle with it.
let d ="M";
for(let x = -50; x<=50; x+=1){
d+=`${x}, ${5*Math.cos(x/5)} `
}
d+="L50,-50L-50,-50z"
pth.setAttribute("d",d);
<svg viewBox="-50 -50 100 100" width="200">
<clipPath id="clip">
<path id="pth"/>
</clipPath>
<circle r="45" clip-path="url(#clip)" fill="blue"/>
</svg>
In order to better understand how I'm building the path please take a look at the next example:
let d ="M";
for(let x = -50; x<=50; x+=1){
d+=`${x}, ${5*Math.cos(x/5)} `
}
d+="L50,-50L-50,-50z"
pth.setAttribute("d",d);
<svg viewBox="-50 -50 100 100" width="200">
<circle r="45" fill="blue"/>
<path id="pth" fill="rgba(250,0,0,.4)"/>
</svg>
Related
I just came across a weired case of bouncing box calculation and it seems I did not grasp the whole truth yet.
First of all, a bounding box is defined as the tightest box, an untransformed element can be enclosed with.
I always was under the impression, that for groups, that means, that it gets basically the union of the bounding box of all children.
However, today I came across this:
<g id="outer">
<g id="inner" transform="translate(100, 100)">
<rect x="0" y="0" width="100" height="100" />
</g>
</g>
The bounding boxes of the elements are as follows:
rect: x: 0, y: 0, w: 100, h: 100
#inner: x: 0, y: 0, w: 100, h: 100
#outer: x: 100, y: 100, w: 100, h: 100
My expectation would have been, that all boxes are the same but as you can see, the outer box is NOT the union of the inner elements (in that case it would equal the #inner's bbox). Instead it takes into account the transformation of the inner elements.
So, is it right to say, that the bbox of a group is the union of the TRANSFORMED bbox's of its children? Or more programatically said, the union of all getBoundingClientRect calls (assuming that scroll is 0 because getCoundingClientRect ignores scroll)?
I would really appreciate a link pointing me to the correct part of the specs.
The bounding box returned by getBBox is the box in the element's transformed coordinate system
Returns the tight bounding box in current user space (i.e., after application of the ‘transform’ attribute, if any) on the geometry of all contained graphics elements, exclusive of stroking, clipping, masking and filter effects)...
The outer SVG element has a different co-ordinate system. I.e. where it places the origin is not the same as the inner <g> element because of the inner element's transform.
getBoundingClientRect operates in the global co-ordinate system however.
In this demo the red polygon represents the #outer BBox during an animation where the rect is rotating.
const SVG_NS = 'http://www.w3.org/2000/svg';
let o = outer.getBBox()
let i = inner.getBBox()
let BBpoly = drawBBox(o);
function drawBBox(bb){
let p = [{x:bb.x,y:bb.y},
{x:bb.x+bb.width,y:bb.y},
{x:bb.x+bb.width,y:bb.y+bb.height},
{x:bb.x,y:bb.y+bb.height}];
let BBpoly = drawPolygon(p, BBoxes);
return BBpoly;
}
function drawPolygon(p, parent) {
let poly = document.createElementNS(SVG_NS, 'polygon');
let ry = [];
for (var i = 0; i < p.length; i++) {
ry.push(String(p[i].x + ", " + p[i].y));
}
var points = ry.join(" ");
poly.setAttributeNS(null, 'points', points);
parent.appendChild(poly);
return poly;
}
function updatePolygon(p,poly){
let ry = [];
for (var i = 0; i < p.length; i++) {
ry.push(String(p[i].x + ", " + p[i].y));
}
var points = ry.join(" ");
poly.setAttributeNS(null, 'points', points);
}
let a = 0;
function Frame(){
requestAnimationFrame(Frame);
inner.setAttributeNS(null,"transform", `rotate(${a}, 120,120)`)
let bb = outer.getBBox()
let p = [{x:bb.x,y:bb.y},
{x:bb.x+bb.width,y:bb.y},
{x:bb.x+bb.width,y:bb.y+bb.height},
{x:bb.x,y:bb.y+bb.height}];
updatePolygon(p,BBpoly);
a++
}
Frame()
svg{border:1px solid; width:300px;}
polygon{fill:none; stroke:red; }
<svg viewBox="0 0 250 250">
<g id="BBoxes"></g>
<g id="outer">
<g id="inner">
<rect x="70" y="70" width="100" height="100" />
</g>
</g>
</svg>
I'm trying to animate lines with stroke-dashoffset and stroke-dasharray attributes. It's complicate figure with circles and line-connectors. I use Snapsvg, here is my figure, it was simplified for example:
<svg version="1.1" id="fImg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 345 320">
<g id="balls">
<circle cx="125" cy="9.3" r="6.7"/>
<circle cx="230.2" cy="63.5" r="6.7"/>
<circle cx="211.6" cy="18.3" r="6.7"/>
<circle cx="292.6" cy="63.5" r="6.7"/>
</g>
<g id="lines">
<line class="st0" x1="103.8" y1="11.6" x2="125" y2="9.3"/>
<line class="st0" x1="103.8" y1="11.6" x2="115.8" y2="44.9"/>
<line class="st0" x1="103.8" y1="11.6" x2="85.9" y2="32.4"/>
<line class="st0" x1="85.9" y1="32.4" x2="115.8" y2="44.9"/>
</g>
</svg>
I use Snap function to set attributes for circles and animate it. lineDrow - callback function for lines, but it works only before .animate({.....}, 1000):
var balls = Snap("#balls");
balls.attr({
fill:'rgba(0,0,0,0)'
}).animate({
fill: '#ccc'
}, 2000, mina.easeout, lineDraw);
function lineDraw() {
var lines = document.querySelectorAll("#lines line");
for (var i = 0; i < lines.length; i++) {
var line = Snap(lines[i]),
x = Math.ceil(line.getTotalLength());
line.attr({
'strokeDasharray': x,
'strokeDashoffset': x,
'stroke-width': 1,
'stroke-linecap':'round',
'stroke': '#ccc'
}).animate({
'strokeDasharray': 0,
'strokeDashoffset': 0
}, 1000);
}
}
And it makes me crease! What's wrong?
There's a couple of issues with your code.
Firstly, you can't use getTotalLength on a line I don't think, so you'll have to create a small function or something to calculate the length of a line (or use paths, as getTotalLength works there).
Snap doesn't have strokeDasharray and strokeDashoffset as animatable attributes (unless it does in the latest version), however, we can animate between any 2 values with the Snap.animate method
Snap.animate( from, to, function, duration, callback). Note from and to can also be arrays of values to interpolate between, eg [0,0,0] to [3,100,5].
So just using the Snap.animate method, we can interpolate from the line length to 0. Note, I've had to use an immediate mode function closure in this case, just as you are using it with lots of different lines, and we want to make sure each animation has the line in scope when called (otherwise it will only animate the last line). If you only have 1 thing to animate, you can get rid of the extra function/closure code.
So it would look something like this (just swap in a value for the line lengths instead of 100), this was a similar question I think you maybe asked in the Snap slack channel which I answered there. The code should be similar enough still to match your amended code.
(function() {
var lineClosure = line;
Snap.animate(100, 0, function( val ){
lineClosure.attr({
'strokeDasharray': val,
'strokeDashoffset': val,
});
}, 1000);
})();
jsfiddle
Thanks, now it works:
function lineDraw() {
function mult(e) {return e*e;}
var lines = $("#lines line");
for (var i = 0; i < lines.length; i++) {
var line = Snap(lines[i]);
var x = (Math.ceil(Math.sqrt(
mult(lines[i].x2.baseVal.value - lines[i].x1.baseVal.value) +
mult(lines[i].y2.baseVal.value - lines[i].y1.baseVal.value) )));
line.attr({
'strokeDasharray': x,
'strokeDashoffset': x,
'stroke-width': 1,
'stroke-linecap':'round',
'stroke': '#ccc'
});
(function() {
var lineClosure = line;
Snap.animate(x, 0, function( val ){
lineClosure.attr({
'strokeDashoffset': val,
});
}, 2000);
})();
}
}
I am trying to do a function in order to get and arc on a SVG path.
I created a function named drawArcSegment which takes 4 params startPercentage, endPercentage, radius, thickness, and I have some pseudo code done
this is what I have already but I don't have an idea of how to ahead
var drawArcSegment = function(startPercentage, endPercentage, radius, thickness) {
point1 = (R*cos(A), R*sin(A));
point2 = ( (R-D)*cos(A), (R-D)*sin(A) );
point3 = ( (R-D)*cos(B), (R-D)*sin(B) );
point4 = (R*cos(B), R*sin(B));
d="M10 10 H 90 V 90 H 10 L 10 10";
$("#myPath").attr("d", d);
}
the html part
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg">
<path stroke="black"
stroke-dasharray="5,5"
fill="green"
id="myPath" />
</svg>
with the value of the d var, I get a square now. But I need something like this:
I have this Pen in case you want to take a look.
So, any suggestion?
I was working in Raphael Js to create a layout in svg. I have created some circles that are arranged in rows and column. Say there are 15 circles in columns and 5 in rows like in the image attached.
Here all the elements are first drawn in a straight line. This is good and as wished. But in some sets drawn, I would want to skew the whole set and also arrange them in a curve (horizontal/vertical). I was trying to use a range slider to determine the curve of the elements.
How can I achieve it?
draw arch (or any complex path)
get path's (x,y) value
plot circles using path. QED.
NOTE: In Raphael, use paper.circle(..) instead of svgdoc.appendChild() with getCircle().
function getCircle(x,y, width, height) {
var xmlns = "http://www.w3.org/2000/svg";
var xlink = "http://www.w3.org/1999/xlink";
var Elem; // ouput SVG element
Elem = document.createElementNS(xmlns,"use");
Elem.setAttributeNS(null, 'x', x);
Elem.setAttributeNS(null, 'y', y);
Elem.setAttributeNS(null, 'width', width);
Elem.setAttributeNS(null, 'height', height);
Elem.setAttributeNS(xlink, 'xlink:href', '#sym01');
return Elem;
}
var svgdoc = document.getElementById('mySVG');
var curve1 = document.getElementById('curve1');
var len = curve1.getTotalLength();
for (var y_pos = 0; y_pos < 10; ++y_pos) {
for (var i = 0; i <= 20; ++i) {
var pt = curve1.getPointAtLength( (i*5/100)*len);
svgdoc.appendChild(getCircle(pt.x-5,pt.y+y_pos*20, 10,10));
}
}
<svg id='mySVG' width="400" height="400">
<!-- symbol definition NEVER draw -->
<symbol id="sym01" viewBox="0 0 40 40">
<circle cx="20" cy="20" r="17" stroke-width="2" stroke="red" fill="pink"/>
</symbol>
<path id="curve1" d="M10 100 Q 200 -10 390 100" stroke="red" fill="transparent"/>
<!-- actual drawing with "use" element -->
</svg>
I am working on svg based application. here user can draw different shapes like rect,circle,line etc. I need to convert these shapes into 'path' and then i need all points in the path.
My solution is working fine for all shapes, except 'circle'. once user finishes the drawing, i convert the circle to 'path' using 'arc'. and when i try to find path point using 'getPointAtLength' , i get different values in different browser. which changes the end result.
So far, 'Opera' displaying best result. below is my code. I have three questions:-
1) How i can fix this?
2) is there any alternative way to find all points in svg path?
3) is there any other way to represent circle in form of path?
jsFiddle Link is http://jsfiddle.net/xfpDA/
<body>
<script>
function roundDecimalNumber(number, places) {
var place;
if (!places)
place = 3;
else
place = places;
number=parseFloat(number);
number = number.toFixed(place);
number = parseFloat(number);
return number;
}
function path2Array(node) {
var pointsArray = new Array();
var point,tx,ty,cordinatesXY;
for (var i = 0; i < node.getTotalLength(); i+=0.2) {
point = node.getPointAtLength(i);
tx=roundDecimalNumber(point.x);
ty=roundDecimalNumber(point.y);
cordinatesXY = {
x: tx,
y: ty
};
pointsArray.push(cordinatesXY);
}
return pointsArray;
}
</script>
<svg id="svg" width="800" height="600" viewBox="528 139 268 192">
<path id="square" fill="none" stroke="blue" d="M 636.866, 216.994 a 25.490,29.18 0 1,0 50.981,0 a 25.490,29.180 0 1,0 -50.981,0 z"/>
</svg>
<script>
var svg=document.getElementById('svg');
var pathSquare=document.getElementById('square');
pathPoints=path2Array(pathSquare);
var d='';
var point;
for(var i=0;i<pathPoints.length;i++){
point=pathPoints[i];
if(i===0)
{
d='M '+point.x+','+point.y+' L ';
}
else{
d+=point.x+' '+point.y+' ';
}
}
var dynamicSquare=document.createElementNS("http://www.w3.org/2000/svg", "path");
dynamicSquare.setAttribute("d", d);
dynamicSquare.setAttribute("fill", 'none');
dynamicSquare.setAttribute("stroke", 'red');
dynamicSquare.setAttribute("transform", 'matrix(1 0 0 1 100 0)');
svg.appendChild(dynamicSquare);
</script>
</body>
i am on ubuntu and firefox version is 28.0