65.9K
CodeProject 正在变化。 阅读更多。
Home

生成随机 Voronoi 图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.41/5 (10投票s)

2017 年 6 月 4 日

CPOL

7分钟阅读

viewsIcon

21358

downloadIcon

68

生成和绘制随机 Voronoi 图,并提供支持的网页和 R 脚本。

引言

Voronoi 图(VD)可以是一种非常多彩的几何图形,我们将使用 JavaScript 和 R 来生成和绘制它的随机版本。

根据维基百科 [1]

"在数学中,Voronoi 图是将平面根据与平面中特定子集中的点的距离进行划分的区域。该点集(称为种子、站点或生成器)是预先指定的,对于每个种子都有一个相应的区域,该区域由所有比任何其他点都更靠近该种子的点组成。"

您会惊讶于存在如此多的“现实世界”和理论应用 [2-4]。在 [2] 中强调,有“数以百计的用于构建各种类型 Voronoi 图的算法”。因此,让我们普遍地讨论算法,并特别应用于 VD 的生成/绘制。

有很多建议,例如在 JavaScript 中使用 D3.js 库 [5],或者在 R 中使用这个或那个包。

您可以在 C、C++、C# 和 VB 中找到许多实现的算法,甚至在这里 CP [6-10] 上也可以找到实际的代码示例。它们几乎都充满了数学知识,代码量“庞大”[6,7] 等。 [9] 中的那个肯定不简单,而且我不确定 [8] 中的那个是否快。但我确信:在我的电脑上,JavaScript 中的生成/绘制比 R 中的更快,即

提示: 生成/绘制的速度取决于语言/工具和您的计算机的性能,即使对于相同的算法也是如此。

在 R 语言中,您可能会完全感到困惑,因为 R 的强大之处之一是其无限的软件包,几乎数千个。对于任何大小的应用程序,您都可以找到一个软件包。在 VD 的情况下,有许多软件包,例如:deldir、alphahull、dismo、ggplot、ggplot2、tripack、CGAL 等。更不用说您还需要安装所有相关的软件包。您需要随机颜色吗?再说一次,再找几个软件包……

事实上,我只在 JavaScript 和 R 中找到了黑白的 VD。

我很幸运地找到了我需要的东西,而不是推荐的工具:算法。

长话短说。我在 [2] 中阅读了“最简单”算法的描述,并在一开始就停了下来。我曾想:“有趣,但太耗时了……”之后,我遇到了 [6,7],并想:“哇,即使理解它也要花费太多时间……”后来我“扫描”了 [5] 中的脚本,并找到了一个小型、紧凑的 Python 代码。我的想法是:“它看起来像假的,太简单了……它不能工作!?测试一下!”于是,我花了 5 分钟创建了第一个非常简单的原型,它奏效了!

在下载的源代码文件中找到一个与初始原型页面几乎相同的页面。

请看下面的原型页面脚本代码。与初始原型页面相比,此版本甚至略有扩展。

function Metric(x,y) {return (x*x + y*y)}
// Generating and plotting Voronoi diagram (simple version).
function pVoronoiD() {
  var cvs=document.getElementById("cvsId");
  var ctx=cvs.getContext("2d");
  var n=7, w=640, x=y=d=dm=j=0, w1=w-2;
  // Arrays for 7 predefined sites (located on canvas) and colors
  X=[20,30,60,80,100,120,140];
  Y=[200,100,300,400,200,400,340];
  C=["red","orange","yellow","green","blue","navy","violet"];
  ctx.fillStyle="white"; ctx.fillRect(0,0,w,w);
  // Plotting sites and applying linked color.
  for(y=0; y<w1; y++) {
    for(x=0; x<w1; x++) {
      dm=w1*w1 + w1*w1; j=-1;
      for(var i=0; i<n; i++) {
        d=Metric(X[i]-x,Y[i]-y)
        if(d<dm) {dm=d; j=i;}
      }//fend i
      ctx.fillStyle=C[j]; ctx.fillRect(x,y,1,1);
    }//fend x
  }//fend y
  // Plotting site points (in black). Note: these 7 lines were added later! LOL
  // Also, there was no title, h3 and comment tags. No JS comments too. Only 21 lines.
  // I've re-formatted it later to look like now (and match prime page code).
  ctx.fillStyle="black";
  for(var i=0; i<n; i++) {
    ctx.fillRect(X[i],Y[i],3,3);
  }
}

VD 生成的所有“魔力”都发生在三重“for”循环(见上文)内部。

简而言之,它的工作原理如下:

  • 对于画布中的每个点 (x,y),都会计算到所有“站点点”(在 X 和 Y 数组中定义)的最小距离。
  • 将与距离最小的“站点点”相关的颜色应用于当前的画布点。
  • 稍后,颜色的重叠有助于确定所有站点的形状。

由这个简单脚本生成的 VD 如下所示。

  原型页面中显示的 Voronoi 图。 

VD, sites 7, Euclidean metric

记住:

提示: 紧凑、简单且可靠的算法是绘图(以及其他)最重要的编程顾问。

此外,一些自定义的辅助函数可以简化代码,并且它们可以用于任何其他应用程序,甚至可以“翻译”成其他语言,例如我们这里的 R 语言。

现在,VD 生成/绘制在 JavaScript 和 R 中都可用,可以生成各种合理数量站点的精美彩色图。这意味着,实际上,有两个“VD 生成器”可供 CP 用户使用。

在 JavaScript 和 R 中实现的算法

HTML 页面中的 JavaScript

让我们仔细看看 JavaScript

// Helper Functions
// HF#1 Like in PARI/GP: return random number 0..max-1
function randgp(max) {return Math.floor(Math.random()*max)}
// HF#2 Random hex color returned as "#RRGGBB"
function randhclr() {
  return "#"+
  ("00"+randgp(256).toString(16)).slice(-2)+
  ("00"+randgp(256).toString(16)).slice(-2)+
  ("00"+randgp(256).toString(16)).slice(-2)
}
// HF#3 Return the value of a metric: Euclidean, Manhattan or Minkovski
function Metric(x,y,mt) {
  if(mt==1) {return Math.sqrt(x*x + y*y)}
  if(mt==2) {return Math.abs(x) + Math.abs(y)}
  if(mt==3) {return(Math.pow(Math.pow(Math.abs(x),3) + Math.pow(Math.abs(y),3),0.33333))}
}
// Generating and plotting Voronoi diagram.
function pVoronoiD() {
  var cvs=document.getElementById("cvsId");
  var ctx=cvs.getContext("2d");
  var w=cvs.width, h=cvs.height;
  var x=y=d=dm=j=0, w1=w-2, h1=h-2;
  var n=document.getElementById("sites").value;
  var mt=document.getElementById("mt").value;
  // Building 3 arrays for requested n random sites (located on canvas)
  // and linked to them random colors.
  var X=new Array(n), Y=new Array(n), C=new Array(n);
  ctx.fillStyle="white"; ctx.fillRect(0,0,w,h);
  for(var i=0; i<n; i++) {
    X[i]=randgp(w1); Y[i]=randgp(h1); C[i]=randhclr();
  }
  // Plotting sites and applying selected mt metric and linked colors.
  for(y=0; y<h1; y++) {
    for(x=0; x<w1; x++) {
      dm=Metric(h1,w1,mt); j=-1;
      for(var i=0; i<n; i++) {
        d=Metric(X[i]-x,Y[i]-y,mt)
        if(d<dm) {dm=d; j=i;}
      }//fend i
      ctx.fillStyle=C[j]; ctx.fillRect(x,y,1,1);
    }//fend x
  }//fend y
  // Plotting site points (in black).
  ctx.fillStyle="black";
  for(var i=0; i<n; i++) {
    ctx.fillRect(X[i],Y[i],3,3);
  }
}

如您所见,3 个辅助函数非常小且不言自明。注意:前两个来自 VOE.js,描述在 [6] 中,并“按原样”使用。

主要的生成和绘制函数 pVoronoiD() 包含 3 个简单步骤:

  • 构建 3 个数组,用于请求的 n 个随机站点(由位于画布上的点定义)和与之关联的随机颜色。
  • 绘制站点:应用选定的 mt 度量和关联的颜色。
  • 绘制站点点(黑色),覆盖已绘制的站点。

事实上,代码的所有主要片段在这里和原型脚本中都是相同的,例如 3 个数组、三重“for”循环、绘制“站点点”。

您可以在下图本身中查看 4 个用 JavaScript 绘制的 Voronoi 图。应用的度量如下:欧几里得、曼哈顿、闵可夫斯基和再次欧几里得。

像往常一样,右键单击任何 VD 图像,您可以将其保存为 png 文件,例如。原始尺寸为 640 x 640 像素,即这里显示的尺寸的 4.6 倍。

  图 1 - 前 3 个 VD 各有 20 个站点,最后一个有 100 个站点 

VD, sites 20, Euclidean metric VD, sites 20, Manhattan metric VD, sites 20, Minkovski metric VD, sites 100, Euclidean metric

R 脚本

R 脚本中的函数看起来与 JavaScript 函数“孪生”,因为它们是从 JavaScript“翻译”过来的。您自己看看

## VORDgenerator.R

## Voronoi Diagram Generator

#  Helper Functions
## HF#1 Random hex color returned as "#RRGGBB".
randHclr <- function() {
  m=255;r=g=b=0;
  r <- sample(0:m, 1, replace=TRUE);
  g <- sample(0:m, 1, replace=TRUE);
  b <- sample(0:m, 1, replace=TRUE);
  return(rgb(r,g,b,maxColorValue=m));
}
## HF#2 Return the value of a metric: Euclidean, Manhattan or Minkovski
Metric <- function(x, y, mt) {
  if(mt==1) {return(sqrt(x*x + y*y))}
  if(mt==2) {return(abs(x) + abs(y))}
  if(mt==3) {return((abs(x)^3 + abs(y)^3)^0.33333)}
}

## Generating and plotting Voronoi diagram.
## ns - number of sites, fn - file name, ttl - plot title.
## mt - type of metric: 1 - Euclidean, 2 - Manhattan, 3 - Minkovski.
pVoronoiD <- function(ns, fn="", ttl="",mt=1) {
  cat(" *** START VD:", date(), "\n");
  if(mt<1||mt>3) {mt=1}; mts=""; if(mt>1) {mts=paste0(", mt - ",mt)};
  m=640; i=j=k=m1=m-2; x=y=d=dm=0;
  if(fn=="") {pf=paste0("VDR", mt, ns, ".png")} else {pf=paste0(fn, ".png")};
  if(ttl=="") {ttl=paste0("Voronoi diagram, sites - ", ns, mts)};
  cat(" *** Plot file -", pf, "title:", ttl, "\n");
  plot(NA, xlim=c(0,m), ylim=c(0,m), xlab="", ylab="", main=ttl);
  # Building 3 arrays for requested n random sites (located on canvas)
  # and linked to them random colors.
  X=numeric(ns); Y=numeric(ns); C=numeric(ns);
  for(i in 1:ns) {
    X[i]=sample(0:m1, 1, replace=TRUE);
    Y[i]=sample(0:m1, 1, replace=TRUE);
    C[i]=randHclr();
  }
  # Plotting sites and applying selected mt metric and linked colors.
  for(i in 0:m1) {
    for(j in 0:m1) {
      dm=Metric(m1,m1,mt); k=-1;
      for(n in 1:ns) {
        d=Metric(X[n]-j,Y[n]-i, mt);
        if(d<dm) {dm=d; k=n;}
      }
      clr=C[k]; segments(j, i, j, i, col=clr);
    }
  }
  # Plotting site points (in black).
  points(X, Y, pch = 19, col = "black", bg = "white")
  dev.copy(png, filename=pf, width=m, height=m);
  dev.off(); graphics.off();
  cat(" *** END VD:",date(),"\n");
}
## Executing:
#pVoronoiD(10)           ## Euclidean metric
#pVoronoiD(10,"","",2)   ## Manhattan metric
#pVoronoiD(10,"","",3)   ## Minkovski metric
#pVoronoiD(150)          ## Euclidean metric

正如您所见,R 中的所有函数都具有与 JavaScript 相同的函数功能和相同的步骤,因为它们被适应了 R 的本地运算符和函数。就是这样!

您可以在下图本身中查看 4 个用 R 绘制的 Voronoi 图。

  图 2 - 前 3 个 VD 各有 10 个站点,最后一个有 150 个站点 

VD, sites 10, Euclidean metric VD, sites 10, Manhattan metric VD, sites 10, Minkovski metric VD, sites 150, Euclidean metric

结论

提供了两个 Voronoi 图生成器:HTML/JavaScript 页面和 R 脚本,这就是这个项目的全部源代码。

应该强调的是,开头引用的 [1] 中的 VD 定义只是众多可能性之一,特别是如果 VD 用于显示温度、水或更抽象的东西。度量也可能与任何距离无关,而是充当“爱情计”或“情绪计”,例如。

无论如何,在许多研究中,随机选择的站点和颜色应该是预定义的,而不是随机的。甚至“点”的顺序也可以改变以适应绘图算法。要确定如何重新排列站点以进行适当的绘图,请观看绘图过程的动画。如果您的计算机不是超快的,您可以在“RGui”窗口的“R Graphics”子窗口中观看“动画”。

这意味着脚本需要修改。幸运的是,这些脚本很小且清晰,因此修改它们并不难。

注意:“现实世界”或理论应用不在本文讨论范围内。

所呈现的算法肯定可靠,因为它们已经支持 7 种语言 [5]。另一个合理的问题是:“它适合您的应用任务吗?”

要找到答案:用您的语言实现它并进行测试!别无他法。

最后,使用“VD 生成器”作为模板,任何人都可以开始任何适合将结果显示为彩色 Voronoi 图的研究。

参考文献

  1. Voronoi 图,维基百科,免费百科全书,网址:https://en.wikipedia.org/wiki/Voronoi_diagram。
  2. Drysdale D. Voronoi Diagrams: Applications from Archaology to Zoology. (1993), Regional Geometry Institute, Smith College. 网址:http://www.ics.uci.edu/~eppstein/gina/scot.drysdale.html。
  3. Snow, J. On the Mode of Communication of Cholera. (1855), London: John Churchill. 网址:http://www.ph.ucla.edu/epi/snow/snowbook.html。
  4. Voronoi, G. Nouvelles applications des paramètres continus à la théorie des formes quadratiques.. (1907), J. reine angew. Math. 133, 97-178。
  5. Voronoi 图,Rosetta Code Wiki,网址:http://rosettacode.org/wiki/Voronoi_diagram。
  6. Haugland. K. Create a Voronoi diagram 1 of 2. (2012), Code Project, 网址:https://codeproject.org.cn/Articles/411242/Create-a-Voronoi-diagram-of。
  7. Haugland. K. Create a Voronoi diagram 2 of 3. (2012), Code Project, 网址:https://codeproject.org.cn/Articles/413452/Create-a-Voronoi-diagram-of。
  8. Joukhadar, B. Fast Voronoi Diagram in C#. (2014), Code Project, 网址:https://codeproject.org.cn/Tips/797123/Fast-Voronoi-Diagram-in-Csharp。
  9. Stadnik, V. Simple approach to Voronoi diagrams. (2015), Code Project, 网址:https://codeproject.org.cn/Articles/882739/Simple-approach-to-Voronoi-diagrams。
  10. BenDi. Fortune's Voronoi algorithm implemented in C#. (2015), Code Project, 网址:https://codeproject.org.cn/Articles/11275/Fortune-s-Voronoi-algorithm-implemented-in-C。
  11. Voevudko, A.E. Generating Kronecker Product Based Fractals. (2017), Code Project, 网址:https://codeproject.org.cn/Articles/1189288/Generating-Kronecker-Product-Based-Fractals。
© . All rights reserved.