source: node_modules/d3-contour/src/contours.js@ e4c61dd

Last change on this file since e4c61dd was e4c61dd, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Prototype 1.1

  • Property mode set to 100644
File size: 6.8 KB
Line 
1import {extent, nice, thresholdSturges, ticks} from "d3-array";
2import {slice} from "./array.js";
3import ascending from "./ascending.js";
4import area from "./area.js";
5import constant from "./constant.js";
6import contains from "./contains.js";
7import noop from "./noop.js";
8
9var cases = [
10 [],
11 [[[1.0, 1.5], [0.5, 1.0]]],
12 [[[1.5, 1.0], [1.0, 1.5]]],
13 [[[1.5, 1.0], [0.5, 1.0]]],
14 [[[1.0, 0.5], [1.5, 1.0]]],
15 [[[1.0, 1.5], [0.5, 1.0]], [[1.0, 0.5], [1.5, 1.0]]],
16 [[[1.0, 0.5], [1.0, 1.5]]],
17 [[[1.0, 0.5], [0.5, 1.0]]],
18 [[[0.5, 1.0], [1.0, 0.5]]],
19 [[[1.0, 1.5], [1.0, 0.5]]],
20 [[[0.5, 1.0], [1.0, 0.5]], [[1.5, 1.0], [1.0, 1.5]]],
21 [[[1.5, 1.0], [1.0, 0.5]]],
22 [[[0.5, 1.0], [1.5, 1.0]]],
23 [[[1.0, 1.5], [1.5, 1.0]]],
24 [[[0.5, 1.0], [1.0, 1.5]]],
25 []
26];
27
28export default function() {
29 var dx = 1,
30 dy = 1,
31 threshold = thresholdSturges,
32 smooth = smoothLinear;
33
34 function contours(values) {
35 var tz = threshold(values);
36
37 // Convert number of thresholds into uniform thresholds.
38 if (!Array.isArray(tz)) {
39 const e = extent(values, finite);
40 tz = ticks(...nice(e[0], e[1], tz), tz);
41 while (tz[tz.length - 1] >= e[1]) tz.pop();
42 while (tz[1] < e[0]) tz.shift();
43 } else {
44 tz = tz.slice().sort(ascending);
45 }
46
47 return tz.map(value => contour(values, value));
48 }
49
50 // Accumulate, smooth contour rings, assign holes to exterior rings.
51 // Based on https://github.com/mbostock/shapefile/blob/v0.6.2/shp/polygon.js
52 function contour(values, value) {
53 const v = value == null ? NaN : +value;
54 if (isNaN(v)) throw new Error(`invalid value: ${value}`);
55
56 var polygons = [],
57 holes = [];
58
59 isorings(values, v, function(ring) {
60 smooth(ring, values, v);
61 if (area(ring) > 0) polygons.push([ring]);
62 else holes.push(ring);
63 });
64
65 holes.forEach(function(hole) {
66 for (var i = 0, n = polygons.length, polygon; i < n; ++i) {
67 if (contains((polygon = polygons[i])[0], hole) !== -1) {
68 polygon.push(hole);
69 return;
70 }
71 }
72 });
73
74 return {
75 type: "MultiPolygon",
76 value: value,
77 coordinates: polygons
78 };
79 }
80
81 // Marching squares with isolines stitched into rings.
82 // Based on https://github.com/topojson/topojson-client/blob/v3.0.0/src/stitch.js
83 function isorings(values, value, callback) {
84 var fragmentByStart = new Array,
85 fragmentByEnd = new Array,
86 x, y, t0, t1, t2, t3;
87
88 // Special case for the first row (y = -1, t2 = t3 = 0).
89 x = y = -1;
90 t1 = above(values[0], value);
91 cases[t1 << 1].forEach(stitch);
92 while (++x < dx - 1) {
93 t0 = t1, t1 = above(values[x + 1], value);
94 cases[t0 | t1 << 1].forEach(stitch);
95 }
96 cases[t1 << 0].forEach(stitch);
97
98 // General case for the intermediate rows.
99 while (++y < dy - 1) {
100 x = -1;
101 t1 = above(values[y * dx + dx], value);
102 t2 = above(values[y * dx], value);
103 cases[t1 << 1 | t2 << 2].forEach(stitch);
104 while (++x < dx - 1) {
105 t0 = t1, t1 = above(values[y * dx + dx + x + 1], value);
106 t3 = t2, t2 = above(values[y * dx + x + 1], value);
107 cases[t0 | t1 << 1 | t2 << 2 | t3 << 3].forEach(stitch);
108 }
109 cases[t1 | t2 << 3].forEach(stitch);
110 }
111
112 // Special case for the last row (y = dy - 1, t0 = t1 = 0).
113 x = -1;
114 t2 = values[y * dx] >= value;
115 cases[t2 << 2].forEach(stitch);
116 while (++x < dx - 1) {
117 t3 = t2, t2 = above(values[y * dx + x + 1], value);
118 cases[t2 << 2 | t3 << 3].forEach(stitch);
119 }
120 cases[t2 << 3].forEach(stitch);
121
122 function stitch(line) {
123 var start = [line[0][0] + x, line[0][1] + y],
124 end = [line[1][0] + x, line[1][1] + y],
125 startIndex = index(start),
126 endIndex = index(end),
127 f, g;
128 if (f = fragmentByEnd[startIndex]) {
129 if (g = fragmentByStart[endIndex]) {
130 delete fragmentByEnd[f.end];
131 delete fragmentByStart[g.start];
132 if (f === g) {
133 f.ring.push(end);
134 callback(f.ring);
135 } else {
136 fragmentByStart[f.start] = fragmentByEnd[g.end] = {start: f.start, end: g.end, ring: f.ring.concat(g.ring)};
137 }
138 } else {
139 delete fragmentByEnd[f.end];
140 f.ring.push(end);
141 fragmentByEnd[f.end = endIndex] = f;
142 }
143 } else if (f = fragmentByStart[endIndex]) {
144 if (g = fragmentByEnd[startIndex]) {
145 delete fragmentByStart[f.start];
146 delete fragmentByEnd[g.end];
147 if (f === g) {
148 f.ring.push(end);
149 callback(f.ring);
150 } else {
151 fragmentByStart[g.start] = fragmentByEnd[f.end] = {start: g.start, end: f.end, ring: g.ring.concat(f.ring)};
152 }
153 } else {
154 delete fragmentByStart[f.start];
155 f.ring.unshift(start);
156 fragmentByStart[f.start = startIndex] = f;
157 }
158 } else {
159 fragmentByStart[startIndex] = fragmentByEnd[endIndex] = {start: startIndex, end: endIndex, ring: [start, end]};
160 }
161 }
162 }
163
164 function index(point) {
165 return point[0] * 2 + point[1] * (dx + 1) * 4;
166 }
167
168 function smoothLinear(ring, values, value) {
169 ring.forEach(function(point) {
170 var x = point[0],
171 y = point[1],
172 xt = x | 0,
173 yt = y | 0,
174 v1 = valid(values[yt * dx + xt]);
175 if (x > 0 && x < dx && xt === x) {
176 point[0] = smooth1(x, valid(values[yt * dx + xt - 1]), v1, value);
177 }
178 if (y > 0 && y < dy && yt === y) {
179 point[1] = smooth1(y, valid(values[(yt - 1) * dx + xt]), v1, value);
180 }
181 });
182 }
183
184 contours.contour = contour;
185
186 contours.size = function(_) {
187 if (!arguments.length) return [dx, dy];
188 var _0 = Math.floor(_[0]), _1 = Math.floor(_[1]);
189 if (!(_0 >= 0 && _1 >= 0)) throw new Error("invalid size");
190 return dx = _0, dy = _1, contours;
191 };
192
193 contours.thresholds = function(_) {
194 return arguments.length ? (threshold = typeof _ === "function" ? _ : Array.isArray(_) ? constant(slice.call(_)) : constant(_), contours) : threshold;
195 };
196
197 contours.smooth = function(_) {
198 return arguments.length ? (smooth = _ ? smoothLinear : noop, contours) : smooth === smoothLinear;
199 };
200
201 return contours;
202}
203
204// When computing the extent, ignore infinite values (as well as invalid ones).
205function finite(x) {
206 return isFinite(x) ? x : NaN;
207}
208
209// Is the (possibly invalid) x greater than or equal to the (known valid) value?
210// Treat any invalid value as below negative infinity.
211function above(x, value) {
212 return x == null ? false : +x >= value;
213}
214
215// During smoothing, treat any invalid value as negative infinity.
216function valid(v) {
217 return v == null || isNaN(v = +v) ? -Infinity : v;
218}
219
220function smooth1(x, v0, v1, value) {
221 const a = value - v0;
222 const b = v1 - v0;
223 const d = isFinite(a) || isFinite(b) ? a / b : Math.sign(a) / Math.sign(b);
224 return isNaN(d) ? x : x + d - 0.5;
225}
Note: See TracBrowser for help on using the repository browser.