d3: Contour or Surface plot from Irregular/Scattered Data
Asked Answered
W

1

6

I can take a set of triplets [X,Y,Z] and immediately generate a (smooth) contour plot using Python and matplotlib with a single call to tricontour(). One can also generate contours 'easily' using plot.ly, but I find it to be unacceptably slow. (Also, I'm not interested in the MATLAB solution, which is similar to the Python)

I'm looking for similar functionality using d3.js. I would settle for a "surface plot" instead of contours, or a "heat map" without contour lines.

I can see how to generate a colored Delaunay triangulation and/or a colored Voronoi Tesselation, but the question of how to generate a contour plot in d3 from irregular data points seems to still be an open one (even though the question on this was prematurely closed!).

So far, all I've seen are approaches "by hand", using Radial basis functions (gaussian blur) or grid interpolation using Barycentric interpolation.

I'd even be willing to 'live with' Gouraud-shading or Coon-gradients on a Delaunay triangulation, but apparently "advanced shading methods" like Gourand or Coon gradients are not in "regular" SVG but are proposed for SVG2...not sure where that leaves me with d3 & (regular) SVG. It seems like doing this SVG gradient-shading by hand would be a major pain.

Is there a "better" package-y way to do this, i.e. something that doesn't require so much 'custom' code? (Maybe via some multidimensional Bezier routine I haven't found yet?)

I'll post a Fiddle with my starting point: a colored Voronoi tesselation: https://jsfiddle.net/k2v2jy7s/1/. Can you help me take this from "blocky" to "smooth" (and maybe even show contour lines)?

<svg width="960" height="500"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height");

    var npoints = 1000;
    var sites = d3.range(npoints)
        .map(function(d) { return [Math.random() * width, Math.random() * height]; });


    // values  at data points / colors being mapped = "zvals"
    var kx = 3.14159/(width*0.5);
    var ky = 3.14159/(height*0.5);
    var zvals = d3.range(npoints)
    for (i = 0; i < npoints; i++) { 
        zvals[i] = (1.0 + Math.cos(kx*sites[i][0]) * Math.cos(ky*sites[i][1]))/2.0;
        zvals[i] *= zvals[i];
    }

    var g = svg.append("g")
        .attr("transform", "translate(" + 0+ "," + 0 + ")");

    var voronoi = d3.voronoi()
        .extent([[-1, -1], [width + 1, height + 1]]);

    var polygon = svg.append("g")
        .attr("class", "polygons")
        .selectAll("path")
        .data(voronoi.polygons(sites))
        .enter().append("path")
        .style('fill', function(d,i){ return d3.hsl( zvals[i]*310,        1, .5); })
        .call(redrawPolygon);

    function redrawPolygon(polygon) {
      polygon
          .attr("d", function(d) { return d ? "M" + d.join("L") + "Z" : null; });
    }
</script>

Update: Also found this blocks.org post on "Gradient Heatmaps", which as I mentioned is the sort of result I'd be willing to live with, but again that's a large quantity of custom code. Would really prefer a compact 'stock' solution, a la tricontour().

Willodeanwilloughby answered 20/6, 2017 at 20:56 Comment(0)
R
0

5 ½ years, and no answers to this question!

Well, I've also been looking into how to generate contours from a series of [X,Y,Z] points in Javascript, but have not yet found the best or most complete solution. A lot of solutions I came across via Googling (such as d3-contour) are designed for an evenly spaced grid of values, not an irregular series of points as you might obtain from a land survey.

d3-tricontour

The d3-tricontour library looks perhaps the most promising, though, so I might have a play around with it.

Here's an example of what it can generate:

d3-tricontour library example

(The labels are optional.)

Apparently it uses the delaunay and meandering triangles algorithms to convert arbitrary points into triangles and then contour geometry. The algorithm works in O(n) where n is the number of edges, meaning it's very fast and scales perfectly well.

To learn more you can visit their:

Alternatives

Otherwise, there might be other ways to do this. If working with one of the grid-based libraries, I think the general process would be to:

  1. Convert arbitary [X,Y,Z] points into a grid — the Delaunay algorithm is probably a great place to start (see d3-delaunay or other delaunay libraries)
  2. Find the Z value for each point in the grid using some kind of interpolation (the maths for that, I'm not sure about)
  3. Then feed that result into one of the grid-based contouring libraries

Constraining Contours

Also take note that creating contours from real world terrain also requires "constraining" some edges so that contours don't crossover ridgelines where they shouldn't.

CDT-JS is a library web app (with no separate library available as yet) that calculates constrained Delaunay triangulation, which might be useful for this case.

Otherwise, in theory, you might be able to create this kind of functionality by injecting additional [X,Y,Z] points along your lines of contraint prior to rendering. But I haven't tested this approach.

Responsory answered 10/2, 2023 at 10:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.