将不同大小的圆形打包成矩形 - d3.js

2023-11-29

我试图打包圈子不同尺寸放入一个长方形容器中,不包装在圆形容器中d3.js捆绑在一起,在下面d3.layout.pack.

这是我想要实现的布局:

我找到了这张纸在这个问题上,但我不是数学家,无法彻底理解这篇文章并将其转换为代码......

任何人都可以建议我应该从哪里开始将其转换为 d3.js 布局插件,或者如果您有类似于此布局的可视化气泡,请建议您解决该问题的任何方向。

谢谢。


这是您的算法的实现。

我对它做了很多调整,但我认为它基本上做了同样的事情。

边界圆

我使用了一个技巧来使计算更加规则。

我没有使用定义边界框的线段,而是使用具有“无限”半径的圆,这可以被认为是线的良好近似:

bounding circles

该图显示了半径减小时 4 个边界圆的样子。经过计算,它们会穿过边界框的角点,并在半径增大时向实际边收敛。

“角”圆(算法如此称呼它们)都被计算为一对圆的切线,从而消除了特殊的圆+线段或线段+线段情况。

这也大大简化了启动条件。
该算法简单地从四个边界圆开始,然后一次添加一个圆,使用贪婪启发式 lambda 参数来选择“最佳”位置。

最适合策略

原始算法不会产生容纳所有圆的最小矩形
(它只是尝试将一堆圆放入给定的矩形中)。

我在其顶部添加了一个简单的二分搜索来猜测最小表面(这会产生给定纵横比的最小边界矩形)。

The code

Here is a fiddle

var Packer = function (circles, ratio)
{
    this.circles = circles;
    this.ratio   = ratio || 1;
    this.list = this.solve();
}

Packer.prototype = {
    // try to fit all circles into a rectangle of a given surface
    compute: function (surface)
    {
        // check if a circle is inside our rectangle
        function in_rect (radius, center)
        {
            if (center.x - radius < - w/2) return false;
            if (center.x + radius >   w/2) return false;
            if (center.y - radius < - h/2) return false;
            if (center.y + radius >   h/2) return false;
            return true;
        }

        // approximate a segment with an "infinite" radius circle
        function bounding_circle (x0, y0, x1, y1)
        {
            var xm = Math.abs ((x1-x0)*w);
            var ym = Math.abs ((y1-y0)*h);
            var m = xm > ym ? xm : ym;
            var theta = Math.asin(m/4/bounding_r);
            var r = bounding_r * Math.cos (theta);
            return new Circle (bounding_r, 
                new Point (r*(y0-y1)/2+(x0+x1)*w/4, 
                           r*(x1-x0)/2+(y0+y1)*h/4));
        }

        // return the corner placements for two circles
        function corner (radius, c1, c2)
        {
            var u = c1.c.vect(c2.c); // c1 to c2 vector
            var A = u.norm();
            if (A == 0) return [] // same centers
            u = u.mult(1/A); // c1 to c2 unary vector
            // compute c1 and c2 intersection coordinates in (u,v) base
            var B = c1.r+radius;
            var C = c2.r+radius;
            if (A > (B + C)) return []; // too far apart
            var x = (A + (B*B-C*C)/A)/2;
            var y = Math.sqrt (B*B - x*x);
            var base = c1.c.add (u.mult(x));

            var res = [];
            var p1 = new Point (base.x -u.y * y, base.y + u.x * y);
            var p2 = new Point (base.x +u.y * y, base.y - u.x * y);
            if (in_rect(radius, p1)) res.push(new Circle (radius, p1));
            if (in_rect(radius, p2)) res.push(new Circle (radius, p2));
            return res;
        }

        /////////////////////////////////////////////////////////////////

        // deduce starting dimensions from surface
        var bounding_r = Math.sqrt(surface) * 100; // "infinite" radius
        var w = this.w = Math.sqrt (surface * this.ratio);
        var h = this.h = this.w/this.ratio;

        // place our bounding circles
        var placed=[
            bounding_circle ( 1,  1,  1, -1),
            bounding_circle ( 1, -1, -1, -1),
            bounding_circle (-1, -1, -1,  1),
            bounding_circle (-1,  1,  1,  1)];

        // Initialize our rectangles list
        var unplaced = this.circles.slice(0); // clones the array
        while (unplaced.length > 0)
        {
            // compute all possible placements of the unplaced circles
            var lambda = {};
            var circle = {};
            for (var i = 0 ; i != unplaced.length ; i++)
            {
                var lambda_min = 1e10;
                lambda[i] = -1e10;
                // match current circle against all possible pairs of placed circles
                for (var j = 0   ; j < placed.length ; j++)
                for (var k = j+1 ; k < placed.length ; k++)
                {
                    // find corner placement
                    var corners = corner (unplaced[i], placed[j], placed[k]);

                    // check each placement
                    for (var c = 0 ; c != corners.length ; c++)
                    {
                        // check for overlap and compute min distance
                        var d_min = 1e10;
                        for (var l = 0 ; l != placed.length ; l++)
                        {
                            // skip the two circles used for the placement
                            if (l==j || l==k) continue;

                            // compute distance from current circle
                            var d = placed[l].distance (corners[c]);
                            if (d < 0) break; // circles overlap

                            if (d < d_min) d_min = d;
                        }
                        if (l == placed.length) // no overlap
                        {
                            if (d_min < lambda_min)
                            {
                                lambda_min = d_min;
                                lambda[i] = 1- d_min/unplaced[i];
                                circle[i] = corners[c];
                            }
                        }
                    }
                }
            }

            // select the circle with maximal gain
            var lambda_max = -1e10;
            var i_max = -1;
            for (var i = 0 ; i != unplaced.length ; i++)
            {
                if (lambda[i] > lambda_max)
                {
                    lambda_max = lambda[i];
                    i_max = i;
                }
            }

            // failure if no circle fits
            if (i_max == -1) break;

            // place the selected circle
            unplaced.splice(i_max,1);
            placed.push (circle[i_max]);
        }

        // return all placed circles except the four bounding circles
        this.tmp_bounds = placed.splice (0, 4);
        return placed;
    },

    // find the smallest rectangle to fit all circles
    solve: function ()
    {
        // compute total surface of the circles
        var surface = 0;
        for (var i = 0 ; i != this.circles.length ; i++)
        {
            surface += Math.PI * Math.pow(this.circles[i],2);
        }

        // set a suitable precision
        var limit = surface/1000;

        var step = surface/2;
        var res = [];
        while (step > limit)
        {
            var placement = this.compute.call (this, surface);
console.log ("placed",placement.length,"out of",this.circles.length,"for surface", surface);
            if (placement.length != this.circles.length)
            {
                surface += step;
            }
            else
            {
                res = placement;
                this.bounds = this.tmp_bounds;
                surface -= step;
            }
            step /= 2;
        }
        return res; 
    }
};

表现

该代码未进行优化,以提高可读性(或者我希望如此:))。

计算时间急剧增加。
您可以安全地放置大约 20 个圆圈,但任何超过 100 个圆圈都会使您的浏览器爬行。

由于某种原因,FireFox 上的速度比 IE11 上快得多。

包装效率

该算法在相同大小的圆上效果很差(它无法找到正方形中 20 个圆的著名蜂窝图案),但在广泛的随机半径分布上效果很好。

美学

对于相同大小的圆圈来说,结果是相当难看的。
没有尝试将圆圈聚集在一起,因此如果算法认为两种可能性相同,则只是随机选择一种。

我怀疑 lambda 参数可以稍微细化一下,以便在值相等的情况下提供更美观的选择。

可能的演变

通过“无限半径”技巧,可以定义任意边界多边形。

如果您提供一个函数来检查圆是否适合所述多边形,则该算法没有理由不产生结果。

这个结果是否有效是另一个问题:)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将不同大小的圆形打包成矩形 - d3.js 的相关文章

随机推荐

  • 用 csv.DictWriter 写入部分行?

    我有一个包含一组输入的 CSV 文件 Example A B C D 我想分析结果并为每一行输出一个 CSV 文件 例如 B C The DictReader构建完整的字典 其中包含键 A B C D The DictWriter按预期设置
  • #define _UNICODE 不适用于 MinGW + CodeBlocks

    通常我使用 Visual Studio 但我切换到 mingw 我喜欢使我的应用程序可以轻松地从 unicode 和多字节更改 在我的 mingw 项目中我有我的定义并包含如下内容 define WIN32 LEAN AND MEAN de
  • 解析多个配置文件的最佳实践

    解析多个配置文件的最佳实践是什么 如果有的话 我想解析mysql服务器配置并重新编写配置 该配置允许发出多行 例如 includedir etc mysql d 有趣的是 某些配置可能位于主文件中 但其他配置可能位于子文件中 我认为 pyp
  • 使用 PHP 将 URL 中的空格替换为 %20

    我希望用 20 替换 url 中的所有空格实例 我将如何使用正则表达式做到这一点 谢谢你 如果您只想用另一个字符串替换一段字符串 则无需使用正则表达式 使用str replace 应该绰绰有余 new str replace 20 your
  • Django:如何让 South 为添加到 INSTALL_APPS 的第三方应用程序创建表?

    我正在尝试使用django 图像裁剪器 Link 在我的项目中 我将其添加到settings py中的INSTALL APPS中并成功解决 该应用程序需要一些数据库表才能使用 所以我必须创建它们 由于我一直在使用 South 因此我需要使用
  • iOS 库到 BitCode

    我最近下载了 Xcode 7 beta Xcode 抱怨我的一些 C 库没有编译成 BitCode 我该如何告诉 Clang 生成与 iOS 兼容的 BitCode 我在 stackoverflow 上看到过类似的答案 但我不知道它们是否适
  • 如何将元组数据提取为单元素格式

    我从以下内容中得到了良好的结果 但是如何从元组中提取该数据 换句话说 如何清理数据 这是数据库里的数据 我跑出来了 gt gt gt policy id 2309L 118L 94L gt gt gt for i in policy id
  • Visual Studio代码EPERM操作不允许

    每次我尝试在 vsc 上安装新扩展时 我都会得到 Error while loading extensions EPERM operation not permitted 接下来它告诉我打开一个 obsolete 文件 但它告诉我的文件路径
  • 在没有 Java EE 应用服务器的情况下使用 Web 服务在 C# 和 Java 之间进行互操作?

    我的处境很困难 我们有一个公开基于 Java 的 API 的第三方企业系统 然而 我们是一个100 Net 导向的开发团队 本质上 我需要用 C 代码可以调用的东西来包装 Java API Web 服务固然很棒 但我们的基础设施上唯一支持的
  • 从网址中删除 web/app_dev.php/

    我已经在 symfony 2 中完成了我的应用程序 现在我想从网址中删除 web app dev php 我读到了这一点 并在这样做之后 php app console cache clear env prod no debug 并添加 h
  • 创建 libcurl http post 表单

    我如何创建一个curl form 例如在stackoverflow上发帖 如果我查看问题表单页面的来源 我会看到
  • 有没有办法获取队列中的最后一个元素?

    我知道堆栈是最好也是最简单的方法 但是是否有可能获得队列中的最后一个元素而无需将任何内容出列 您可以简单地执行以下操作 Assumes T is a reference type if it s a value type then you
  • 删除文本文件中的特定行

    我正在研究一个选项 如果用户输入确切的标题和作者 该选项将能够删除指定的行 但是我无法让它发挥作用 我的功能内容如下所示 fnRemoveBook echo Title read Title echo Author read Author
  • 如何在java中从tcp流播放声音

    还有另一个应用程序在此套接字上写入原始 wav 文件 客户端启动并开始收听当前正在播放的歌曲 Socket clientSocket new Socket localhost 9595 AudioInputStream stream Aud
  • TypeScript 错误 TS2339:“EventTarget”类型上不存在属性“matches”

    我收到一个我无法从 TypeScript 中理解的错误 我正在使用一段完全有效的 JavaScript 但它在我的 IDE 中以及通过 Gulp 进行预处理期间都标记了错误 我已将其剥离回其核心 但仍然收到错误 即使这是完全有效的 JS d
  • 将段落的每一行包裹在一个跨度中

    我有一个 div 元素 它将显示一个没有换行符的段落 如示例中所示 div Lorem Ipsum is simply dummy text of the printing and typesetting industry Lorem Ip
  • 无法同时满足约束 - 没有适当的约束

    我已经检查并删除了每个用户限制 但仍然收到以下错误ONLY旋转设备后 我完全不知道为什么 有人有什么想法吗 2013 01 14 21 30 31 363 myApp 35869 c07 Unable to simultaneously s
  • 声纳添加新项目

    我正在尝试添加一个新项目到sonar 运行声纳跑步者时 我收到以下错误 任何人都可以帮助我解决这个问题 sonar runner Runner configuration file opt lampp htdocs typo3 sonar
  • 如何在不使用 SQLAlchemy 引擎的情况下将数据帧写入 Postgres 表?

    我有一个数据框 我想写入Postgres数据库 此功能需要成为Flask app 现在 我通过创建一个单独的脚本来运行此插入部分SQLAlchemy 引擎并将其传递给df to sql 将数据框写入数据库表 但是当我将此功能集成到 Flas
  • 将不同大小的圆形打包成矩形 - d3.js

    我试图打包圈子不同尺寸放入一个长方形容器中 不包装在圆形容器中d3 js捆绑在一起 在下面d3 layout pack 这是我想要实现的布局 我找到了这张纸在这个问题上 但我不是数学家 无法彻底理解这篇文章并将其转换为代码 任何人都可以建议