如何从中心圆的顶部开始在中心圆周围绘制x个圆圈?

时间:2014-04-01 07:08:13

标签: javascript html math trigonometry

我正在尝试创建一个包含大量圆圈项目的UI。有时这些圈子会有相关的圆圈,应该在它们周围显示。

我能够拼凑出有效的东西here

问题是外圈开始接近0度,我希望它们以函数/库的消费者提供的角度开始。我从来不是三角学或几何学的明星,所以我可以使用一些帮助。

正如您在消费代码中所看到的,有一个设置:startingDegree: 270函数getPosition应该遵守,但我无法弄清楚如何。

更新04/02/2014:

正如我在对Salix alba的评论中提到的那样,我上面并不清楚,但我需要的是能够指定卫星圆的半径,并让它们只是部分地一直走。 Salix提供了一种解决方案,可以计算出卫星围绕中心圆均匀配合所需的尺寸。

使用Salix的答案中的一些提示,我能够达到预期的效果......并且有一个额外的“模式”,感谢Salix,将来。

工作但仍然很粗糙的解决方案是:http://jsfiddle.net/RD4RZ/11/。这是整个代码(就这样全部都是在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
                    //a-A-B
                    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>

1 个答案:

答案 0 :(得分:4)

你需要解决的中心位是小圆的半径。如果中心圆的半径为R,并且您希望在其周围放置n个较小的圆圈。让小圆圈的未知半径为r。我们可以构造一个直角三角形,其中一个角在大圆的中心,一个在小圆的中心,另一个是从中心的一条直线与小圆相切的地方。这将是一个直角。中心的角度为a,斜边的长度为R+r,相反的是r,我们不需要相邻的角度。使用trig

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

重新排列

(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))

一旦我们r,我们已经完成了很多工作。

您可以将其视为小提琴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();