block by nitaku dd611c0e4dae1d38eab67aedae7ac44f

Cushion Voronoi

Full Screen

A Voronoi diagram colored with a radial gradient centered on each cell’s site. The luminance of each point is proportional to the cost of reaching the nearest site (the darker the color, the higher the cost).

index.js

// Generated by CoffeeScript 1.10.0
(function() {
  var cells, cells_data, defs, enter_gradients, gradients, height, radius, sites, sites_data, svg, voronoi, width;

  svg = d3.select('svg');

  width = svg.node().getBoundingClientRect().width;

  height = svg.node().getBoundingClientRect().height;

  defs = svg.append('defs');

  radius = 10;

  sites_data = d3.range(20).map(function() {
    return {
      x: Math.round(Math.random() * (width - radius * 2) + radius),
      y: Math.round(Math.random() * (height - radius * 2) + radius)
    };
  });

  voronoi = d3.voronoi().x(function(d) {
    return d.x;
  }).y(function(d) {
    return d.y;
  }).extent([[-1, -1], [width + 1, height + 1]]);

  cells_data = voronoi.polygons(sites_data);

  gradients = defs.selectAll('.gradient').data(sites_data);

  enter_gradients = gradients.enter().append('radialGradient').attrs({
    "class": 'gradient',
    gradientUnits: 'userSpaceOnUse',
    id: function(d, i) {
      return "gradient_" + i;
    },
    cx: function(d) {
      return d.x;
    },
    cy: function(d) {
      return d.y;
    },
    r: 300
  });

  enter_gradients.append('stop').attrs({
    offset: '0%',
    'stop-color': 'white'
  });

  enter_gradients.append('stop').attrs({
    offset: '100%',
    'stop-color': 'black'
  });

  cells = svg.selectAll('.cell').data(cells_data);

  cells.enter().append('path').attrs({
    "class": 'cell',
    d: function(d) {
      if (d == null) {
        return null;
      } else {
        return "M" + d.join("L") + "Z";
      }
    },
    fill: function(d, i) {
      return "url(#gradient_" + i + ")";
    }
  });

  sites = svg.selectAll('.site').data(sites_data);

  sites.enter().append('circle').attrs({
    "class": 'site',
    r: radius,
    cx: function(d) {
      return d.x;
    },
    cy: function(d) {
      return d.y;
    }
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Cushion Voronoi</title>
  <link type="text/css" href="index.css" rel="stylesheet"/>
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
</head>
<body>
  <svg></svg>
  <script src="index.js"></script>
</body>
</html>

index.coffee

svg = d3.select 'svg'
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height

defs = svg.append 'defs'
    
radius = 10

sites_data = d3.range(20).map () ->
  return {
    x: Math.round(Math.random() * (width - radius * 2) + radius),
    y: Math.round(Math.random() * (height - radius * 2) + radius)
  }

voronoi = d3.voronoi()
  .x (d) -> d.x
  .y (d) -> d.y
  .extent [[-1, -1], [width + 1, height + 1]]
  
cells_data = voronoi.polygons(sites_data)

# define cells gradients
gradients = defs.selectAll '.gradient'
  .data sites_data
  
enter_gradients = gradients.enter().append 'radialGradient'
  .attrs
    class: 'gradient'
    gradientUnits: 'userSpaceOnUse'
    id: (d,i) -> "gradient_#{i}"
    cx: (d) -> d.x
    cy: (d) -> d.y
    r: 300
    
enter_gradients.append 'stop'
  .attrs
    offset: '0%'
    'stop-color': 'white'
    
enter_gradients.append 'stop'
  .attrs
    offset: '100%'
    'stop-color': 'black'

# render cells
cells = svg.selectAll '.cell'
  .data cells_data
  
cells.enter().append 'path'
  .attrs
    class: 'cell'
    d: (d) ->  if not d? then null else "M" + d.join("L") + "Z"
    fill: (d,i) -> "url(#gradient_#{i})"


# render sites
sites = svg.selectAll '.site'
  .data sites_data
  
sites.enter().append 'circle'
  .attrs
    class: 'site'
    r: radius
    cx: (d) -> d.x
    cy: (d) -> d.y
    

index.css

body, html {
  padding: 0;
  margin: 0;
  height: 100%;
}
svg {
  width: 100%;
  height: 100%;
  background: white;
}

.site {
  fill: #DDD;
  stroke: gray;
}
.cell {
  shape-rendering: crispEdges;
}