How to create an arc using JavaScript some maths and SVG? - javascript

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?

Related

Fill the intersection area between circle and function in JSXGraph

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>

SVG arc slider for range input

My goal is to design an arc slider which looks something like that
I have the following structure of the template
<svg width="500" height="300">
<path id="track" stroke="lightgrey" fill="transparent" stroke-width="20" d="
M 50 50
A 90 90 0 0 0 300 50
"/>
<path id="trackFill" fill="cyan" stroke-width="20" d="
M 50 50
A 90 90 0 0 0 [some dynamic value?] [some dynamic value?]
"/>
<circle id="knob" fill="lightblue" cx="[dynamic, initial - 50]" cy="[dynamic, initial - 50]" r="25"/>
</svg>
knob - the control which user is supposed to drag in order to change the value
track - the full arc of the slide
trackFill - the portion of the slider path before the knob
Is it possible to make trackFill cover the portion of the slider before the knob as it is being dragged along the slider curve? If so which APIs or CSS rules will help me to achieve such a result?
Is it something like this you are after?
let svg = document.getElementById("slider");
let trackFill = document.getElementById("trackFill");
let knob = document.getElementById("knob");
let isDragging = false;
let sliderDragOffset = {dx: 0, dy: 0};
let ARC_CENTRE = {x: 175, y: 50};
let ARC_RADIUS = 125;
let sliderValue = 0;
setSliderValue(sliderValue);
function setSliderValue(value)
{
// Limit value to (0..sliderMax)
let sliderMax = track.getTotalLength();
sliderValue = Math.max(0, Math.min(value, sliderMax));
// Calculate new position of knob
let knobRotation = sliderValue * Math.PI / sliderMax;
let knobX = ARC_CENTRE.x - Math.cos(knobRotation) * ARC_RADIUS;
let knobY = ARC_CENTRE.y + Math.sin(knobRotation) * ARC_RADIUS;
// Adjust trackFill dash patter to only draw the portion up to the knob position
trackFill.setAttribute("stroke-dasharray", sliderValue + " " + sliderMax);
// Update the knob position
knob.setAttribute("cx", knobX);
knob.setAttribute("cy", knobY);
}
knob.addEventListener("mousedown", evt => {
isDragging = true;
// Remember where we clicked on knob in order to allow accurate dragging
sliderDragOffset.dx = evt.offsetX - knob.cx.baseVal.value;
sliderDragOffset.dy = evt.offsetY - knob.cy.baseVal.value;
// Attach move event to svg, so that it works if you move outside knob circle
svg.addEventListener("mousemove", knobMove);
// Attach move event to window, so that it works if you move outside svg
window.addEventListener("mouseup", knobRelease);
});
function knobMove(evt)
{
// Calculate adjusted drag position
let x = evt.offsetX + sliderDragOffset.dx;
let y = evt.offsetY + sliderDragOffset.dy;
// Position relative to centre of slider arc
x -= ARC_CENTRE.x;
y -= ARC_CENTRE.y;
// Get angle of drag position relative to slider centre
let angle = Math.atan2(y, -x);
// Positions above arc centre will be negative, so handle them gracefully
// by clamping angle to the nearest end of the arc
angle = (angle < -Math.PI / 2) ? Math.PI : (angle < 0) ? 0 : angle;
// Calculate new slider value from this angle (sliderMaxLength * angle / 180deg)
setSliderValue(angle * track.getTotalLength() / Math.PI);
}
function knobRelease(evt)
{
// Cancel event handlers
svg.removeEventListener("mousemove", knobMove);
window.removeEventListener("mouseup", knobRelease);
isDragging = false;
}
<svg id="slider" width="500" height="300">
<g stroke="lightgrey">
<path id="track" fill="transparent" stroke-width="20" d="
M 50 50
A 125 125 0 0 0 300 50
"/>
</g>
<use id="trackFill" xlink:href="#track" stroke="cyan"/>
<circle id="knob" fill="lightblue" cx="50" cy="50" r="25"/>
</svg>
I've kept this code simple for clarity, but at the expense of some limitations.
It assumes there is only one slider per page. If you need more than that, you will have to keep the slider-specific values (eg sliderValue and, isDragging) separate. You could use data attributes for that. You would also need to switch from accessing the SVG elements via id attributes to another way (eg. class attributes), because id attributes must be unique on the page.
Here is a simple example:
const radius = 50;
const offsetX = 10;
const offsetY = 10;
// 0 <= pos <= 1
const setSliderPos = (svg, pos) => {
const angle = Math.PI * pos;
const x = offsetX + radius - Math.cos(angle) * radius;
const y = offsetY + Math.sin(angle) * radius;
svg.select('.knob').attr('cx', x).attr('cy', y);
svg.select('.first').attr('d', `M ${offsetX},${offsetY} A ${radius},${radius} 0 0 0 ${x},${y}`);
svg.select('.second').attr('d', `M ${x},${y} A ${radius},${radius} 0 0 0 ${offsetX + radius * 2},${offsetY}`);
}
setSliderPos(d3.select('#svg-1'), 0.3);
setSliderPos(d3.select('#svg-2'), 0.6);
setSliderPos(d3.select('#svg-3'), 1);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg id="svg-1" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
<svg id="svg-2" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
<svg id="svg-3" width="150" height="80">
<path class="first" stroke-width="5" stroke="lightblue" fill="none"/>
<path class="second" stroke-width="5" stroke="cyan" fill="none"/>
<circle class="knob" r="10" fill="lightblue"/>
</svg>
To mark the progress you can use stroke-dasharray with a percentage; for example
<g stroke="lightgrey">
<path id="track" fill="transparent" stroke-width="20"
stroke-dasharray="40% 60%"
d="M 50 50 A 125 125 0 0 0 300 50"/>
</g>
This will show 40% of the arc and hide 60% of the arc.
If you need to use two colors, for example the whole arc in grey and the progress in black, you need to use two arcs on top of one another; the one at the bottom would be the one you already have, and the one at the top would have a stroke in black and use stroke-dasharray as shown.

How to find the midPoint of arc in SVG with javascript

I want to the mid point of arc in svg.. Can any one tell formula to find the midbpoint of arc.,
The answer to that question is not straightforward. That's because in SVG arcs can be an elliptical arc. For example, what would you say is the midpoint on the following arc?
<svg width="200" height="200" viewBox="0 0 100 100">
<path fill="none" stroke="black" stroke-width="2"
d="M 20,75 A 21,50, 45, 1 1, 60,75"/>
</svg>
Anyway, without getting into complicated formulae, the simplest method is probably to take advantage of SVG's pointAtLength() method.
var myarc = document.getElementById("myarc");
// Get the length of the path
var pathLen = myarc.getTotalLength();
// How far along the path to we want the position?
var pathDistance = pathLen * 0.5;
// Get the X,Y position
var midpoint = myarc.getPointAtLength(pathDistance)
// For fun, let's add a dot at that position to mark it.
var svg = myarc.ownerSVGElement;
var circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", midpoint.x);
circle.setAttribute("cy", midpoint.y);
circle.setAttribute("r", "5");
circle.setAttribute("fill", "red");
svg.appendChild(circle);
<svg width="200" height="200" viewBox="0 0 100 100">
<path id="myarc" fill="none" stroke="black" stroke-width="2"
d="M 20,75 A 21,50, 45, 1 1, 60,75"/>
</svg>

Raphael Js : How to re-arrange the elements inside a raphael set in a curve path

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>

Trying to rotate and transform SVG path - do I need trigonometry calculations?

I'm trying to manipulate with mouse SVG path which represents symbol of electronics resistor. This symbol requires manipulation with end of the "leads" (if you can picture real resistor); therefore I am trying to achieve draging 1st point arround (2nd is still there) and to all points of path to behave proportionally in when drag the 1st point on new coordinates.
Try to group, try with trigonometry functions...so code is like:
`<g id="r" > <!-- R - group for symbol of electronics resistor -->
<path d="M 200 20 v80 h30 v150 h-60 v-150 h30 m 0 150 v80 "
fill="none" stroke="blue" stroke-width="5" />
<circle cx="200" cy="20" r="10"
fill="blue" />
<circle cx="200" cy="330" r="10"
fill="blue"/>
</g>`
Please, help.
I think I've made roughly what you want: http://dl.dropbox.com/u/169269/resistor.svg
Click and drag on the resistor to scale and rotate it to that mouse position. In this version, the line thickness and circles also scale, but it shouldn't be too difficult to avoid that.
It does require trigonometry and transformations. The key part is the drag function, which I explain in more detail at: http://www.petercollingridge.co.uk/interactive-svg-components/draggable-svg-element
function drag(evt)
{
if(selected_element != 0)
{
var resistor_x = 200;
var resistor_y = 100;
var mouse_x = evt.pageX;
var mouse_y = evt.pageY;
dx = resistor_x - mouse_x;
dy = resistor_y - mouse_y;
d = Math.sqrt(dx*dx + dy*dy); // Find distance to mouse
theta = 90+Math.atan2(dy, dx)*180/Math.PI; //Find angle to mouse in degrees
transform = "translate(200, 100) rotate(" + theta + ") scale(" + d/310 + ")" ;
selected_element.setAttributeNS(null, "transform", transform);
}
}
Note that this code assumes the resistor is 310 pixels long and rotating about (200, 100). Scaling and rotation transformations work centred on (0,0), so you should draw the resistor with the static point at (0,0) and then translate it to where you want.

Categories

Resources