Comment dessiner un nombre de cercles x autour d'un cercle central, en commençant par le haut du cercle central?

J'essaie de créer une interface utilisateur qui comporte beaucoup d'éléments en cercles. Parfois, ces cercles auront des cercles apparentés qui devraient être affichés autour d'eux.

J'ai pu regrouper quelque chose qui fonctionne, ici .

Le problème est que les cercles extérieurs commencent près de 0 degrés, et j'aimerais qu'ils commencent à un angle fourni par le consommateur de la fonction / bibliothèque. Je n'ai jamais été une étoile à la trigonométrie, ni à la géométrie, de sorte que je pouvais utiliser un peu d'aide.

Comme vous pouvez le voir dans le code consommateur, il existe un paramètre: getPosition startingDegree: 270 que la fonction getPosition doit honorer, mais je n'ai pas été en mesure de comprendre comment.

Mise à jour 04/02/2014:

Comme je l'ai mentionné dans mon commentaire à Salix alba, je n'étais pas clair au-dessus, mais ce dont j'avais besoin était de pouvoir spécifier le rayon des cercles satellites et de les faire partiellement en partie. Salix a donné une solution qui calcule la taille dont les satellites doivent être pour s'adapter uniformément au cercle central.

À l'aide de quelques conseils sur la réponse de Salix, j'ai pu obtenir le résultat souhaité … et avoir un "mode" supplémentaire, grâce à Salix, dans le futur.

La solution de travail, bien que difficile, est ici: http://jsfiddle.net/RD4RZ/11/ . Voici le code entier (juste pour que ce soit sur SO):

 <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> <script type="text/javascript" src="//code.jquery.com/jquery-1.10.1.js"></script> <style type="text/css"> .circle { position: absolute; width: 100px; height: 100px; background-repeat: no-repeat;background-position: center center; border: 80px solid #a19084; border-radius: 50%; -moz-border-radius: 50%; } .sm { border: 2px solid #a19084; } </style> <script type="text/javascript">//<![CDATA[ $(function () { function sind(x) { return Math.sin(x * Math.PI / 180); } /*the law of cosines: cc = aa + bb - 2ab cos(C), where c is the satellite diameter a and b are the legs solving for cos C, cos C = ( aa + bb - cc ) / 2ab Math.acos((a * a + b * b - c * c) / (2 * a * b)) = C */ function solveAngle(a, b, c) { // Returns angle C using law of cosines var temp = (a * a + b * b - c * c) / (2 * a * b); if (temp >= -1 && temp <= 1) return radToDeg(Math.acos(temp)); else throw "No solution"; } function radToDeg(x) { return x / Math.PI * 180; } function degToRad(x) { return x * (Math.PI / 180); } var satellite = { //settings must have: collection (array), itemDiameter (number), minCenterDiameter (number), center (json with x, y numbers) //optional: itemPadding (number), evenDistribution (boolean), centerPadding (boolean), noOverLap (boolean) getPosition: function (settings) { //backwards compat settings.centerPadding = settings.centerPadding || settings.itemPadding; settings.noOverLap = typeof settings.noOverLap == 'undefined' ? true : settings.noOverLap; settings.startingDegree = settings.startingDegree || 270; settings.startSatellitesOnEdge = typeof settings.startSatellitesOnEdge == 'undefined' ? true : settings.startSatellitesOnEdge; var itemIndex = $.inArray(settings.item, settings.collection); var itemCnt = settings.collection.length; var satelliteSide = settings.itemDiameter + (settings.itemSeparation || 0) + (settings.itemPadding || 0); var evenDistribution = typeof settings.evenDistribution == 'undefined' ? true : settings.evenDistribution; var degreeOfSeparation = (360 / itemCnt); /* we know all three sides: one side is the diameter of the satellite itself (plus any padding). the other two are the parent radius + the radius of the satellite itself (plus any padding). given that, we need to find the angle of separation using the law of cosines (solveAngle) */ //if (!evenDistribution) { var side1 = ((satelliteSide / 2)) + ((settings.minCenterDiameter + (2 * settings.centerPadding)) / 2); var side2 = satelliteSide;; var degreeOfSeparationBasedOnSatellite = solveAngle(side1, side1, side2); //Math.acos(((((side1 * side1) + (side2 * side2)) - (side2 * side2)) / (side2 * side2 * 2)) / 180 * Math.PI) * Math.PI; degreeOfSeparation = evenDistribution? degreeOfSeparation: settings.noOverLap ? Math.min(degreeOfSeparation, degreeOfSeparationBasedOnSatellite) : degreeOfSeparationBasedOnSatellite; //} //angle-angle-side //aAB var a = satelliteSide; var A = degreeOfSeparation; /* the three angles of any triangle add up to 180. We know one angle (degreeOfSeparation) and we know the other two are equivalent to each other, so... */ var B = (180 - A) / 2; //b is length necessary to fit all satellites, might be too short to be outside of base circle var b = a * sind(B) / sind(A); var offset = (settings.itemDiameter / 2) + (settings.itemPadding || 0); // 1; // var onBaseCircleLegLength = ((settings.minCenterDiameter / 2) + settings.centerPadding) + offset; var offBase = false; if (b > onBaseCircleLegLength) { offBase = true; } b = settings.noOverLap ? Math.max(b, onBaseCircleLegLength) : onBaseCircleLegLength; var radianDegree = degToRad(degreeOfSeparation); //log('b=' + b); //log('settings.center.x=' + settings.center.x); //log('settings.center.y=' + settings.center.y); var degreeOffset = settings.startingDegree; if (settings.startSatellitesOnEdge) { degreeOffset += ((offBase ? degreeOfSeparation : degreeOfSeparationBasedOnSatellite) / 2); } var i = ((Math.PI * degreeOffset) / 180) + (radianDegree * itemIndex);// + (degToRad(degreeOfSeparationBasedOnSatellite) / 2); //(radianDegree) * (itemIndex); var x = (Math.cos(i) * b) + (settings.center.x - offset); var y = (Math.sin(i) * b) + (settings.center.y - offset); return { 'x': Math.round(x), 'y': Math.round(y) }; } , /* if we ever want to size satellite by how many need to fit tight around the base circle: x: function calcCircles(n) { circles.splice(0); // clear out old circles var angle = Math.PI / n; var s = Math.sin(angle); var r = baseRadius * s / (1 - s); console.log(angle); console.log(s); console.log(r); console.log(startAngle); console.log(startAngle / (Math.PI * 2)); for (var i = 0; i < n; ++i) { var phi = ((Math.PI * startAngle) / 180) + (angle * i * 2); var cx = 150 + (baseRadius + r) * Math.cos(phi); var cy = 150 + (baseRadius + r) * Math.sin(phi); circles.push(new Circle(cx, cy, r)); } }, */ //settings must have: collection (array), itemDiameter (number), minCenterDiameter (number), center (json with x, y numbers) //optional: itemPadding (number), evenDistribution (boolean), centerPadding (boolean), noOverLap (boolean) getAllPositions: function (settings) { var point; var points = []; var collection = settings.collection; for (var i = 0; i < collection.length; i++) { settings.item = collection[i] points.push(satellite.getPosition(settings)); } return points; } }; var el = $("#center"), cnt = 10, arr = [], itemDiameter= 100; for (var c = 0; c < cnt; c++) { arr.push(c); } var settings = { collection: arr, itemDiameter: itemDiameter, minCenterDiameter: el.width(), center: { x: el.width() / 2, y: el.width() / 2 }, itemPadding: 2, evenDistribution: false, centerPadding: parseInt(el.css("border-width")), noOverLap: false, startingDegree: 270 }; var points = satellite.getAllPositions(settings); for (var i = 0; i < points.length; i++) { var $newdiv1 = $("<div></div>"); var div = el.append($newdiv1); $newdiv1.addClass("circle").addClass("sm"); $newdiv1.text(i); $newdiv1.css({ left: points[i].x, top: points[i].y, width: itemDiameter +'px', height: itemDiameter +'px' }); } });//]]> </script> </head> <body> <div id="center" class="circle" style="left:250px;top:250px" > </div> </body> </html> 

Le bit central dont vous avez besoin de travailler est le rayon des petits cercles. Si vous avez R pour le rayon du cercle central et que vous souhaitez installer des cercles plus petits autour de lui. Laissez le rayon encore inconnu du petit cercle être r . Nous pouvons construire un triangle à angle droit avec un coin dans le centre du grand cercle un au centre du petit cercle et celui où une ligne du centre est tangente au petit cercle. Ce sera un angle droit. L'angle au centre est a l'hypoténuse a la longueur R+r le contraire est r et nous n'avons pas besoin de l'adjacente. Utilisation de trigonométrie

 sin(a) = op / hyp = r / (R + r) 

réarranger

 (R+r) sin(a) = r R sin(a) + r sin(a) = r R sin(a) = r - r sin(a) R sin(a) = (1 - sin(a)) r r = R sin(a) / ( 1 - sin(a)) 

Une fois que nous avons r nous sommes à peu près terminés.

Vous pouvez le voir comme une violon http://jsfiddle.net/SalixAlba/7mAAS/

 // canvas and mousedown related variables var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); var $canvas = $("#canvas"); var canvasOffset = $canvas.offset(); var offsetX = canvasOffset.left; var offsetY = canvasOffset.top; var scrollX = $canvas.scrollLeft(); var scrollY = $canvas.scrollTop(); // save canvas size to vars b/ they're used often var canvasWidth = canvas.width; var canvasHeight = canvas.height; var baseRadius = 50; var baseCircle = new Circle(150,150,50); var nCircles = 7; var startAngle = 15.0; function Circle(x,y,r) { this.x = x; this.y = y; this.r = r; } Circle.prototype.draw = function() { ctx.beginPath(); ctx.arc(this.x,this.y,this.r, 0, 2 * Math.PI, false); ctx.stroke(); } var circles = new Array(); function calcCircles(n) { circles.splice(0); // clear out old circles var angle = Math.PI / n; var s = Math.sin(angle); var r = baseRadius * s / (1-s); console.log(angle); console.log(s); console.log(r); for(var i=0;i<n;++i) { var phi = startAngle + angle * i * 2; var cx = 150+(baseRadius + r) * Math.cos(phi); var cy = 150+(baseRadius + r) * Math.sin(phi); circles.push(new Circle(cx,cy,r)); } } function draw() { baseCircle.draw(); circles.forEach(function(ele){ele.draw()}); } calcCircles(7); draw();