extent.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import {abs, epsilon} from "../math";
  2. import clipBuffer from "./buffer";
  3. import clipLine from "./line";
  4. import clipPolygon from "./polygon";
  5. import {merge} from "d3-array";
  6. var clipMax = 1e9, clipMin = -clipMax;
  7. // TODO Use d3-polygon’s polygonContains here for the ring check?
  8. // TODO Eliminate duplicate buffering in clipBuffer and polygon.push?
  9. export function clipExtent(x0, y0, x1, y1) {
  10. function visible(x, y) {
  11. return x0 <= x && x <= x1 && y0 <= y && y <= y1;
  12. }
  13. function interpolate(from, to, direction, stream) {
  14. var a = 0, a1 = 0;
  15. if (from == null
  16. || (a = corner(from, direction)) !== (a1 = corner(to, direction))
  17. || comparePoint(from, to) < 0 ^ direction > 0) {
  18. do stream.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
  19. while ((a = (a + direction + 4) % 4) !== a1);
  20. } else {
  21. stream.point(to[0], to[1]);
  22. }
  23. }
  24. function corner(p, direction) {
  25. return abs(p[0] - x0) < epsilon ? direction > 0 ? 0 : 3
  26. : abs(p[0] - x1) < epsilon ? direction > 0 ? 2 : 1
  27. : abs(p[1] - y0) < epsilon ? direction > 0 ? 1 : 0
  28. : direction > 0 ? 3 : 2; // abs(p[1] - y1) < epsilon
  29. }
  30. function compareIntersection(a, b) {
  31. return comparePoint(a.x, b.x);
  32. }
  33. function comparePoint(a, b) {
  34. var ca = corner(a, 1),
  35. cb = corner(b, 1);
  36. return ca !== cb ? ca - cb
  37. : ca === 0 ? b[1] - a[1]
  38. : ca === 1 ? a[0] - b[0]
  39. : ca === 2 ? a[1] - b[1]
  40. : b[0] - a[0];
  41. }
  42. return function(stream) {
  43. var activeStream = stream,
  44. bufferStream = clipBuffer(),
  45. segments,
  46. polygon,
  47. ring,
  48. x__, y__, v__, // first point
  49. x_, y_, v_, // previous point
  50. first,
  51. clean;
  52. var clipStream = {
  53. point: point,
  54. lineStart: lineStart,
  55. lineEnd: lineEnd,
  56. polygonStart: polygonStart,
  57. polygonEnd: polygonEnd
  58. };
  59. function point(x, y) {
  60. if (visible(x, y)) activeStream.point(x, y);
  61. }
  62. function polygonInside() {
  63. var winding = 0;
  64. for (var i = 0, n = polygon.length; i < n; ++i) {
  65. for (var ring = polygon[i], j = 1, m = ring.length, point = ring[0], a0, a1, b0 = point[0], b1 = point[1]; j < m; ++j) {
  66. a0 = b0, a1 = b1, point = ring[j], b0 = point[0], b1 = point[1];
  67. if (a1 <= y1) { if (b1 > y1 && (b0 - a0) * (y1 - a1) > (b1 - a1) * (x0 - a0)) ++winding; }
  68. else { if (b1 <= y1 && (b0 - a0) * (y1 - a1) < (b1 - a1) * (x0 - a0)) --winding; }
  69. }
  70. }
  71. return winding;
  72. }
  73. // Buffer geometry within a polygon and then clip it en masse.
  74. function polygonStart() {
  75. activeStream = bufferStream, segments = [], polygon = [], clean = true;
  76. }
  77. function polygonEnd() {
  78. var startInside = polygonInside(),
  79. cleanInside = clean && startInside,
  80. visible = (segments = merge(segments)).length;
  81. if (cleanInside || visible) {
  82. stream.polygonStart();
  83. if (cleanInside) {
  84. stream.lineStart();
  85. interpolate(null, null, 1, stream);
  86. stream.lineEnd();
  87. }
  88. if (visible) {
  89. clipPolygon(segments, compareIntersection, startInside, interpolate, stream);
  90. }
  91. stream.polygonEnd();
  92. }
  93. activeStream = stream, segments = polygon = ring = null;
  94. }
  95. function lineStart() {
  96. clipStream.point = linePoint;
  97. if (polygon) polygon.push(ring = []);
  98. first = true;
  99. v_ = false;
  100. x_ = y_ = NaN;
  101. }
  102. // TODO rather than special-case polygons, simply handle them separately.
  103. // Ideally, coincident intersection points should be jittered to avoid
  104. // clipping issues.
  105. function lineEnd() {
  106. if (segments) {
  107. linePoint(x__, y__);
  108. if (v__ && v_) bufferStream.rejoin();
  109. segments.push(bufferStream.result());
  110. }
  111. clipStream.point = point;
  112. if (v_) activeStream.lineEnd();
  113. }
  114. function linePoint(x, y) {
  115. var v = visible(x, y);
  116. if (polygon) ring.push([x, y]);
  117. if (first) {
  118. x__ = x, y__ = y, v__ = v;
  119. first = false;
  120. if (v) {
  121. activeStream.lineStart();
  122. activeStream.point(x, y);
  123. }
  124. } else {
  125. if (v && v_) activeStream.point(x, y);
  126. else {
  127. var a = [x_ = Math.max(clipMin, Math.min(clipMax, x_)), y_ = Math.max(clipMin, Math.min(clipMax, y_))],
  128. b = [x = Math.max(clipMin, Math.min(clipMax, x)), y = Math.max(clipMin, Math.min(clipMax, y))];
  129. if (clipLine(a, b, x0, y0, x1, y1)) {
  130. if (!v_) {
  131. activeStream.lineStart();
  132. activeStream.point(a[0], a[1]);
  133. }
  134. activeStream.point(b[0], b[1]);
  135. if (!v) activeStream.lineEnd();
  136. clean = false;
  137. } else if (v) {
  138. activeStream.lineStart();
  139. activeStream.point(x, y);
  140. clean = false;
  141. }
  142. }
  143. }
  144. x_ = x, y_ = y, v_ = v;
  145. }
  146. return clipStream;
  147. };
  148. }
  149. export default function() {
  150. var x0 = 0,
  151. y0 = 0,
  152. x1 = 960,
  153. y1 = 500,
  154. cache,
  155. cacheStream,
  156. clip;
  157. return clip = {
  158. stream: function(stream) {
  159. return cache && cacheStream === stream ? cache : cache = clipExtent(x0, y0, x1, y1)(cacheStream = stream);
  160. },
  161. extent: function(_) {
  162. return arguments.length ? (x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1], cache = cacheStream = null, clip) : [[x0, y0], [x1, y1]];
  163. }
  164. };
  165. }