7

What would be the least 'expensive' way to hide all voronoi strokes that fall in the sea?

The strokes (and polygon fill) that run on land should be visible, whilst those that are on sea should be hidden from view. I think my objective should be clear from the picture below:

enter image description here

I can think of two options:

  1. Somehow 'reclip' the voronoi polygons to the underlying country ('land') polygons. That sounds super CPU intensive. This is not a good option, so let's not go there.
  2. Overlay a 'sea' polygon on top of the voronoi tesselation. That would be super effective visually and exactly what I need. How would I go about calculating the new sea polygon from a base map of countries? (e.g. this jsfiddle with a geoJSON D3 map) I have multiple maps with various levels of polygon complexity, so I need a fool-proof way to build that map.

Any ideas?

Noobster
  • 1,024
  • 1
  • 13
  • 28
  • 6
    I think this should give you some direction http://bl.ocks.org/jasondavies/d70baf034448ef7a52d1 – Cyril Cherian Feb 22 '18 at 07:56
  • Is this not going to be super intensive? Heaps of calculations, no? – Noobster Feb 22 '18 at 08:00
  • 2
    Point no 2 is definitely less intensive...but 1st you need to get the path for water bodies. 2nd the overlay path will be so many that it might again slow down your browser.(Not sure though) – Cyril Cherian Feb 22 '18 at 08:11
  • What if you use the same fill color for the sea as the voronoi lines? The effect would be to hide all the lines inside the water... of course, the islands inside sea might be an issue then -- but perhaps that's ok? – SteveR Feb 22 '18 at 13:07
  • Hi SteveR, thanks also for your input. That wouldn't work as I may want to fill the voronoi polygons with a colour. – Noobster Feb 22 '18 at 13:24

1 Answers1

2

A simple option, which requires none of:

  • an ocean polygon
  • redrawing land polygons / merging land polygons
  • clip paths

is to use an svg pattern. This may sound a bit odd, and I'm not positive as to what the performance implications are, but a pattern of your voronoi diagram can be used to fill your countries/features (preserving boundaries and allowing features to be drawn once and only once).

This requires that the polygon fill is not geographic feature dependent but rather voronoi dependent - your image uses the same fill for each polygon, but your question text might be suggesting this is not the case

To use a pattern like this, create a pattern that is the same width and height as your map. In the pattern, place the voronoi diagram paths. Lastly, set the fill of each feature to the pattern:

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height");

var projection = d3.geoMercator()
  .scale((width - 3) / (2 * Math.PI))
  .translate([width / 2, height / 2]);

var path = d3.geoPath()
  .projection(projection);

var graticule = d3.geoGraticule();

d3.json("https://unpkg.com/world-atlas@1/world/50m.json", function(error, world) {
  if (error) throw error;
  
  // random points:
  var circles = d3.range(20).map(function() {
    return {
      x: Math.round(Math.random() * width),
      y: Math.round(Math.random() * height)
    };
  });
 
  // voronoi:
  var voronoi = d3.voronoi()
    .x(function(d) { return d.x; })
    .y(function(d) { return d.y; })
    .extent([[-1, -1], [width + 1, height + 1]]); 
      
      
  // pattern:
  var pattern = svg.append("defs")
    .append("pattern")
    .attr("id","voronoi")
    .attr("patternUnits","userSpaceOnUse")
    .attr("width", width)
    .attr("height",height)
    .attr("x",0)
    .attr("y",0)
    
  pattern.selectAll("path")
    .data(voronoi.polygons(circles))
    .enter()
    .append("path")
    .attr("d", renderCell)
    .attr("fill",function(d,i) { return d3.schemeCategory20[i]; })
  
  // append paths as normal:
  var features = svg.selectAll(null)
    .data(topojson.feature(world,world.objects.countries).features)
    .enter()
    .append("path")
    .attr("class", "boundary")
    .attr("d", path)
    .attr("fill","url(#voronoi)");  // fill with pattern
 
  function renderCell(d) {
    return d == null ? null : "M" + d.join("L") + "Z";
  } 
 
  
});
.boundary {
  stroke: black;
  stroke-width: 1px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
<svg width="600" height="400"></svg>
Andrew Reid
  • 37,021
  • 7
  • 64
  • 83
  • Andrew, your solution is very elegant and ingenious - I am seriously impressed and have already implemented it with success. The only issue is that it slows the script RIGHT down. If you add a zoom function to your script above, you will see what I mean? – Noobster Feb 23 '18 at 10:49
  • Andrew, I have a second issue. When I apply your script it colors the countries in as expected, and as a function of the voronoi diagram. When I zoom out however, it seems to be just 'tiling' the pattern. The weird thing is that when I go into the console and navigate the elements in the pattern, they are located as expected and with the right color. Is this expected behaviour? – Noobster Feb 23 '18 at 11:35
  • I hadn't considered zoom. If you are using a map of the world, but only zooming in on a subset of the world, you'd be tiling the entire thing - including the majority of the world that might not be visible. It might be faster to append a land polygon and fill it with the pattern, and apply an unfilled (but stroked) country polygon on top. Without seeing more specifics it's hard to say. I could look at a more conventional answer if you are using topojson or have a land mass polygon to work with. – Andrew Reid Feb 23 '18 at 22:54
  • Andrew that is seriously insightful, once again. My thoughts are as follows : if I produce a 'land' polygon then in reality I am only one step away from producing a 'sea' polygon, as the sea would just be a rectangle minus the 'hole' that is the land mass. Interesting to hear that the tiling is expected behaviour. In that case, would it not be possible to 'zoom out' until whole world is visible before zooming back in again? – Noobster Feb 24 '18 at 02:18