程序生成形状

时间:2017-04-30 13:24:57

标签: javascript html5-canvas draw

我试图编写一个圆圈,这个圆圈本身由12x30个其他圆圈组成,这些圆圈触及(或者非常接近)但从不相互重叠。这样一个圆圈的每一行应代表一个月,每个圆圈代表一天。所以另外我需要完全控制每个生成的元素以进一步操作它们......

基于我尝试编写类似下面示例的内容。

我做得非常粗糙,并且完全不知道如何编写代码以便它执行一次并生成完整的形状/生成形状。

我想我应该检查圆圈之间的最小距离,然后执行一些函数来绘制下一列?



// window.addEventListener("mousemove", draw);
//
// var mouseX;
// var mouseY;

var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

var strokeWidth = 1;
var radius = 60;
var maxCircle = 12;
var size = 10

var maxCircle2 = 12;
var size2 = 20
var radius2 = 95;

var maxCircle3 = 12;
var size3 = 40
var radius3 = 160;

var maxCircle4 = 12;
var size4 = 65
var radius4 = 270;

ctx.translate(canvas.width/2, canvas.height/2);


//Draw January
for (var i = 0; i <= maxCircle; i++) {
    ctx.beginPath();
    ctx.arc(0, radius, size, -Math.PI/2, 2*Math.PI, false);
    ctx.rotate(2*Math.PI/maxCircle);
    ctx.stroke();
}

for (var i = 0; i <= maxCircle2; i++) {
    ctx.beginPath();
    ctx.arc(0, radius2, size2, -Math.PI/2, 2*Math.PI, false);
    ctx.rotate(2*Math.PI/maxCircle2);
    ctx.stroke();
}

for (var i = 0; i <= maxCircle3; i++) {
    ctx.beginPath();
    ctx.arc(0, radius3, size3, -Math.PI/2, 2*Math.PI, false);
    ctx.rotate(2*Math.PI/maxCircle3);
    ctx.stroke();
}

for (var i = 0; i <= maxCircle4; i++) {
    ctx.beginPath();
    ctx.arc(0, radius4, size4, -Math.PI/2, 2*Math.PI, false);
    ctx.rotate(2*Math.PI/maxCircle4);
    ctx.stroke();
}
&#13;
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body style="background-color: #fff;">

    <canvas id="canvas" width="800" height="500" style="border: 1px solid black;">
    </canvas>

    <script src="script.js"></script>

  </body>
</html>
&#13;
&#13;
&#13;

2 个答案:

答案 0 :(得分:2)

问题:创建由30个同心环组成的形状。每个戒指有12个相同大小的圆圈。

需要根据这些约束条件选择同心环的半径及其圆圈:

  1. 给定一个环形半径session.beginTransaction(); User user = (User) session.createQuery("select * from `user` where email = '"+email+"'"); session.getTransaction().commit(); ,必须选择该环上12个圆的半径r,以便相邻的圆圈只是触摸但不重叠。

  2. 给定一个环半径s,必须选择下一个较大的同心环r的半径,以便两个环上的圆圈只接触但不重叠。

  3. 插图:同心环及其顶部的圆圈以及形成十二边形的圆心之间的连接以相同的颜色绘制:

    enter image description here

    我们知道十二边形的侧角以15°的步长变化。如果我们将半径为r'的圆放置在距中心s的距离处,我们可以使用公式r + s计算给定环的圆半径s = sin(15°) / (1 - sin(15°)) * r半径为s。参见例如https://www.illustrativemathematics.org/content-standards/tasks/710用于几何解释。

    两个圆圈之间的距离等于圆圈的直径r

    应用上述公式并预先计算所有相关因素得出:

    2 * s
    function drawRingsOfCircles(r) {
      var RADIUS_FACTOR = 0.34919818620854987;
      var ARC_START = -0.5 * Math.PI;
      var ARC_END = 2 * Math.PI;
      var ROTATE = Math.PI * 0.16666666666666666;
      
      for (var i = 0; i < 30; i++) {
        var s = RADIUS_FACTOR * r; 
        for (var j = 0; j < 12; j++) {  
          ctx.beginPath();
          ctx.arc(0, r + s, s, ARC_START, ARC_END);
          ctx.rotate(ROTATE);
          ctx.stroke();
        } 
        r = r + s + s;
      }
    }
    
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    
    ctx.translate(canvas.width * 0.5, canvas.height * 0.5);
    drawRingsOfCircles(20);

答案 1 :(得分:0)

下面给出一种可能的解决方案。它是一种蛮力解决方案,通过逐渐计算在主圆内的环中可以安装多少圆,如果无法找到拟合,则可以使用逐渐变小的半径来拟合圆圈。

我已经使用了一年中的几天并展示了这个过程。但是你只需要计算一次拟合半径。如果您知道正确的半径,则拟合代码有效。如果您给出的半径太大,它只会减慢速度。

最有可能是一些计算方法可以让你对起始半径做出很好的猜测但是因为你只是做了一小部分,所以我看不到深入的观点。 (但同样有趣的问题。)

演示代码每100毫秒增加365天,加上定位和渲染时间。每个月都有不同的色调,并且太阳更暗(主要是为了我自己的兴趣)。打包功能位于名为circles的对象reposition中,并根据需要进行调用。其余的只是支持渲染/样式/测试

添加定义圆以适合的主圆,还包括边距(示例中为3个像素),即任何圆之间的最小间距。 (注意中心不到4个圆圈会触及)

请注意,您不必一次添加一个圆圈。如果需要,它会立即完成所有操作。我没有测试它超过400个圆圈所以不知道它会走多远或如何随着圆圈数量变高(10000加)

var ctx = canvas.getContext("2d");
const P = (x, y) => { return {x, y}}; // shorthand point creation function
// qEach is a fast callback itterator.
const qEach = (array,callback) =>{ for(var i = 0; i < array.length; i++){ callback(array[i], i) } };
const setStyle = (ctx,style) => {
    if( style ){ qEach(Object.keys(style), key => {if(ctx[key]){ ctx[key] = style[key] }}) }
    return style;
};
const styles = {
    named : {},
    add(name, style){ return this.named[name] = style },
    addQ(name, strokeStyle, fillStyle, lineWidth){ return this.add(name, {strokeStyle, fillStyle, lineWidth})},
}

var circles = {
    items : [],
    dirty : true, // indicates that this object can not be drawn
    masterCircle : null,
    setFontStyle(fontStyle){ return this.fontStyle = fontStyle },
    createCircle(text,style){
        return {
            radius : 0,
            pos : P(0,0),
            style,text,
        };
    },
    createMaster(radius,pos,margin,style){
        this.dirty = true;
        this.masterCircle = {
            pos,radius,style,margin,
        };
        this.circleRadius = radius / 4;
    },
    clean(){
        if(this.dirty){
            if(this.masterCircle === null){
                throw new RangeError("No master circle");
            }            
            this.reposition();
            this.dirty = false;
        }
    },
    nearest(point){
        this.clean();
        var minDist = Infinity;
        var circle;
        for(var i = 0; i < this.items.length; i ++){
            var x = this.items[i].pos.x - point.x;
            var y = this.items[i].pos.y - point.y;
            var dist = Math.sqrt(x * x + y * y);
            if(minDist > dist){
                minDist = dist;
                circle = this.items[i];
            }
        }
        return circle;
    },
    draw(ctx){
        this.clean();
        setStyle(ctx,this.masterCircle.style);
        ctx.beginPath();
        ctx.arc(this.masterCircle.pos.x, this.masterCircle.pos.y, this.masterCircle.radius, 0, Math.PI * 2);
        if(this.masterCircle.style.fillStyle) { ctx.fill() }
        if(this.masterCircle.style.strokeStyle) { ctx.stroke() }
        for(var i = 0; i < this.items.length; i ++){
            var cir = this.items[i];
            setStyle(ctx,cir.style);
            ctx.beginPath();
            ctx.arc(cir.pos.x, cir.pos.y, cir.radius, 0, Math.PI * 2);
            if(cir.style.fillStyle) { ctx.fill() }
            if(cir.style.strokeStyle) { ctx.stroke() }
        }
        if(this.fontStyle){
            setStyle(ctx,this.fontStyle);
            for(var i = 0; i < this.items.length; i ++){
                var cir = this.items[i];
                if(this.fontStyle.fillStyle){
                    ctx.fillText(cir.text,cir.pos.x, cir.pos.y);
                }
                if(this.fontStyle.strokeStyle){
                    ctx.strokeText(cir.text,cir.pos.x, cir.pos.y);
                }
            }
        }
    },
    reposition(){
        // set circles in position on a ring
        const setRing = (count,start,end,cr) => {
            var angStep = (Math.PI * 2) / count;            
            for(var i = start; i < end; i ++){

                var cir = this.items[i];
                cir.pos.x = Math.cos((i-start) * angStep) * cr + this.masterCircle.pos.x;
                cir.pos.y = Math.sin((i-start) * angStep) * cr + this.masterCircle.pos.y;
                cir.radius = R;
            }
            
        }
        // get the number of items
        var count = this.items.length;
        if(count === 0){ return } // code below can not handle 0 as count
        var positioned = 0;  // number of circle that have been positioned
        var r = this.masterCircle.radius;  // radius
        var m = this.masterCircle.margin;  // margin
        // get last circle radius (save some calculation steps)
        // warning if you remove circles you need to reset circleRadius to a larger size 
        // or the best fit is found for the smaller radius leaving more of a hole in the middle
        var R = this.circleRadius  === undefined ? 45 : this.circleRadius;
        var maxRingCount = 0;  // counts number of rings so to guess at what size the next radius down 
                               // should be if we can not fit the circles
        var protect = 0;  // I am not 100% confident this function will solve all problems
                          // This counter prevents any infinit looping
        // keep trying to fit circles untill min radius (5) or all positioned or protect overflows                          
        while(positioned < this.items.length && R > 5 && protect < 1300){
            protect ++;
            r = this.masterCircle.radius; // Get the outer radius
            positioned = 0;               // reset the number of circles positioned
            count = this.items.length;    // number of circles to position
            var ringCount = 0;            // counts the number of rings
            while(positioned < this.items.length && r > R + m){  // add rings of circles until out of space
                var cr = ((R + m) * count)/(Math.PI);  // get the radius if we fit all circles at current R
                if(cr + R + m > r){   // is this radius greater than the current radius
                    while(cr + R + m > r){  // yes decrease count untill we find a fit
                        count -= 1;
                        cr = ((R + m) * count)/(Math.PI);
                    }
                }
                if(count > 0){  // if we found the number of circle that can fit in a ring inside the radius
                    setRing(count,positioned,positioned + count ,r-m-R);  // add the ring
                    positioned += count        // count the positioned circles
                    count = this.items.length - positioned;  // get the number of circle remaining
                    ringCount += 1;  // count the ring
                    maxRingCount = Math.max(ringCount,maxRingCount)  // keep the max ring count
                }else{
                    break; // could not fit circles. exit this loop
                }
                r -= R * 2 + m;
            }
            if(positioned === this.items.length){  // have all circles been ppositions
                this.circleRadius = R;   // save the current radius that fits all circles
                break; // all done the loop
            }
            R-= 3/maxRingCount;  // could not fit all circles. Reduce the radius and try again.
        }

    },
    add(circle){
        this.dirty = true;
        this.items.push(circle);
        return circle;
    }
}

//Test code adds 365 circles to the ring. 
var hue = -210;
var hueStep = 210;
/*circles.setFontStyle(styles.add("font",{
    font : "18px arial",
    textAlign : "center",
    textBaseline : "middle",
    fillStyle : "black",
}));*/
var masterStyle = styles.addQ("Master","black","hsl(120,90%,90%)",2);
styles.addQ("Jan","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Feb","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Mar","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Apr","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("May","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Jun","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Jul","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Aug","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Sep","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Oct","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Nov","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);
styles.addQ("Dec","black",`hsl(${hue = (hue +  hueStep) % 360},90%,90%)`,2);

hue = -210;
styles.addQ("SatJan","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatFeb","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatMar","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatApr","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatMay","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatJun","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatJul","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatAug","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatSep","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatOct","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatNov","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);
styles.addQ("SatDec","black",`hsl(${hue = (hue +  hueStep) % 360},90%,80%)`,2);

hue = -210;
styles.addQ("SunJan","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunFeb","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunMar","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunApr","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunMay","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunJun","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunJul","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunAug","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunSep","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunOct","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunNov","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);
styles.addQ("SunDec","black",`hsl(${hue = (hue +  hueStep) % 360},90%,70%)`,2);


var months = ["Jan",31,"Feb",28,"Mar",31,"Apr",30,"May",31,"Jun",30,"Jul",31,"Aug",31,"Sep",30,"Oct",31,"Nov",30,"Dec",31];


circles.createMaster( Math.min(canvas.width, canvas.height) / 2 - 4, P(canvas.width / 2, canvas.height / 2),3, styles.named.Master);
circles.draw(ctx)
var count = 0;
var currentStyle;
var currentMonthDayCount = 0;
function addSome(){
    count ++;
    if(currentMonthDayCount === 0){
        if(months.length === 0){
            return;
        }
        currentStyle = months.shift();
        
        currentMonthDayCount = months.shift();
    }
    currentMonthDayCount -= 1;
    ctx.clearRect(0,0,canvas.width,canvas.height);
    if(count %7 === 5){  // saturday style
        circles.add(circles.createCircle(count,styles.named["Sat" + currentStyle]));
    }else if(count %7 === 6){ // sunday style
        circles.add(circles.createCircle(count,styles.named["Sun" + currentStyle]));
    }else{
        circles.add(circles.createCircle(count,styles.named[currentStyle]));
    }
    circles.draw(ctx)
    setTimeout(addSome,100);

}
addSome();
<canvas id="canvas" width=1024 height=1024></canvas>